diff --git a/src/backend/common/errors/MSErrors.ts b/src/backend/common/errors/MSErrors.ts index 915be1be..3a7c5cae 100644 --- a/src/backend/common/errors/MSErrors.ts +++ b/src/backend/common/errors/MSErrors.ts @@ -2,11 +2,13 @@ import { parseRegexSingle } from "@foxxmd/regex-buddy-core"; import mergeErrorCause from 'merge-error-cause'; import { findCauseByFunc, isAbortReasonErrorLike } from "../../utils/ErrorUtils.js"; import { UpstreamError, UpstreamErrorOptions } from "./UpstreamError.js"; -import { isAbortError } from "abort-controller-x"; -import {addKnownErrorConstructor} from 'serialize-error'; +import {addKnownErrorConstructor, serializeError} from 'serialize-error'; export abstract class NamedError extends Error { public abstract name: string; + public toJSON() { + return serializeError(this); + } } export abstract class StageError extends NamedError {} @@ -45,6 +47,9 @@ const STACK_AT_REGEX = new RegExp(/[\n\r]\s*at/); export class SimpleError extends Error implements HasSimpleError { simple: boolean; name = 'SimpleError'; + public toJSON() { + return serializeError(this); + } stackShortened: boolean = false; @@ -113,7 +118,11 @@ export const mergeSimpleError = (err: Error): Error => { // mergeErrorCause mutates the argument // and we want to be able to do more cause/error parsing after merging for logging // so give it a copy instead of the original - return mergeErrorCause(structuredClone(err)); + const mergedErr = mergeErrorCause(structuredClone(err)); + if(!('toJson' in mergedErr)) { + // @ts-expect-error this is used by serialize error with out custom errors + mergedErr.toJSON = () => serializeError(mergedErr); + } } return err; } diff --git a/src/backend/tests/database/drizzle.test.ts b/src/backend/tests/database/drizzle.test.ts index 45cae9cf..f2cfaf9a 100644 --- a/src/backend/tests/database/drizzle.test.ts +++ b/src/backend/tests/database/drizzle.test.ts @@ -563,6 +563,27 @@ describe('Repository Operations', function () { }); +describe('Serializes Errors', function() { + + it('serializes errors correctly', async function () { + + await withLocalTmpDir(async () => { + try { + let [db, _] = await getMigratedDb(getDbPath('ms', process.cwd())); + const component = await db.insert(components).values(fixtureCreateComponent()).returning(); + + const playRepo = new DrizzlePlayRepository(db); + const playData: RepositoryCreatePlayOpts = { ...fixtureCreatePlay({ componentId: component[0].id, play: generatePlayWithLifecycle({ lifecycleSteps: { preCompare: [false] } }) }), state: 'queued', input: { data: undefined } }; + const p = await playRepo.createPlays([playData]); + expect(p[0].play.lifecycle[0].error.cause).is.not.undefined; + } catch (e) { + throw e; + } + }, { unsafeCleanup: true, postfix: 'serializeStageError' }); + }); + +}); + describe('DB Size Stats', function () { diff --git a/src/core/tests/utils/apiFixtures.ts b/src/core/tests/utils/apiFixtures.ts index 99d4f53f..30f61329 100644 --- a/src/core/tests/utils/apiFixtures.ts +++ b/src/core/tests/utils/apiFixtures.ts @@ -78,6 +78,7 @@ export const generateQueueStateApi = (data: Partial): QueueStateA queueStatus: faker.helpers.arrayElement(QUEUE_STATUSES), updatedAt: cAt, retries: 0, + createdAt: cAt, ...data } } diff --git a/src/core/tests/utils/fixtures.ts b/src/core/tests/utils/fixtures.ts index eb2e9782..df8bff38 100644 --- a/src/core/tests/utils/fixtures.ts +++ b/src/core/tests/utils/fixtures.ts @@ -8,7 +8,7 @@ import { diffObjects } from '../../DataUtils.js'; import { existingScrobble } from '../../../backend/utils/PlayComparisonUtils.js'; import { UpstreamError } from '../../../backend/common/errors/UpstreamError.js'; import { playToListenPayload } from '../../../backend/common/vendor/listenbrainz/lzUtils.js'; -import { mergeSimpleError, SimpleError, SkipTransformStageError, StagePrerequisiteError } from '../../../backend/common/errors/MSErrors.js'; +import { mergeSimpleError, SimpleError, SkipTransformStageError, StagePrerequisiteError, StageTransformError } from '../../../backend/common/errors/MSErrors.js'; import dayjs, { Dayjs } from 'dayjs'; import { TransformHook } from '../../../backend/common/infrastructure/Transform.js'; import { serializeError } from 'serialize-error'; @@ -192,7 +192,7 @@ export const generateLifecycleStep = (play: PlayObject, opts: GenerateLifecycleO if(error === true) { step.flowResult = 'stop'; step.flowReason = 'Error encountered while transforming'; - step.error = serializeError(new Error('Failed to do something', {cause: new Error('Oops it borked.')})); + step.error = new StageTransformError('foo', 'Failed to do something', {cause: new Error('Oops it borked.')}); } else if(error === 'prereq') { step.flowResult = 'stop'; step.flowKnownState = 'prereq';