Skip to content

Commit

Permalink
#3216: add tests; clarify use of helper method
Browse files Browse the repository at this point in the history
  • Loading branch information
twschiller committed May 6, 2022
1 parent c53cd0f commit 1fadfdf
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 11 deletions.
8 changes: 4 additions & 4 deletions src/background/logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
IGNORED_ERROR_PATTERNS,
isAxiosError,
isContextError,
serializePixiebrixError,
serializeErrorAndProperties,
} from "@/errors";
import { expectContext, forbidContext } from "@/utils/expectContext";
import { isAppRequest, selectAbsoluteUrl } from "@/services/requestErrorUtils";
Expand Down Expand Up @@ -332,9 +332,9 @@ export async function recordError(
context: flatContext,
message,
data,

// Ensure it's serialized
error: serializePixiebrixError(maybeSerializedError as Error),
// Ensure the object is fully serialized. Required because it will be stored in IDB and flow through the Redux
// state. Can be converted to serializeError after https://github.com/sindresorhus/serialize-error/issues/74
error: serializeErrorAndProperties(maybeSerializedError),
}),
]);
} catch (recordError) {
Expand Down
75 changes: 75 additions & 0 deletions src/blocks/transformers/jq.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (C) 2022 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { JQTransformer } from "@/blocks/transformers/jq";
import { unsafeAssumeValidArg } from "@/runtime/runtimeTypes";
import ConsoleLogger from "@/utils/ConsoleLogger";
import { InputValidationError } from "@/blocks/errors";
import { BusinessError } from "@/errors";

describe("parse compile error", () => {
test("invalid fromdate", async () => {
// https://github.com/pixiebrix/pixiebrix-extension/issues/3216
const promise = new JQTransformer().transform(
unsafeAssumeValidArg({ filter: '"" | fromdate', input: {} }),
{
ctxt: {},
root: null,
logger: new ConsoleLogger(),
}
);

await expect(promise).rejects.toThrowError(InputValidationError);
await expect(promise).rejects.toThrowError(
'date "" does not match format "%Y-%m-%dT%H:%M:%SZ"'
);
});

test("missing brace", async () => {
// https://github.com/pixiebrix/pixiebrix-extension/issues/3216
const promise = new JQTransformer().transform(
unsafeAssumeValidArg({ filter: "{", input: {} }),
{
ctxt: {},
root: null,
logger: new ConsoleLogger(),
}
);

await expect(promise).rejects.toThrowError(InputValidationError);
await expect(promise).rejects.toThrowError(
"Unexpected end of jq filter, are you missing a parentheses, brace, and/or quote mark?"
);
});

test("null iteration", async () => {
// https://github.com/pixiebrix/pixiebrix-extension/issues/3216
const promise = new JQTransformer().transform(
unsafeAssumeValidArg({ filter: ".foo[]", input: {} }),
{
ctxt: {},
root: null,
logger: new ConsoleLogger(),
}
);

await expect(promise).rejects.toThrowError(BusinessError);
await expect(promise).rejects.toThrowError(
"Cannot iterate over null (null)"
);
});
});
7 changes: 6 additions & 1 deletion src/blocks/transformers/jq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { BlockArg, BlockOptions, Schema } from "@/core";
import { propertiesToSchema } from "@/validators/generic";
import { isNullOrBlank } from "@/utils";
import { InputValidationError } from "@/blocks/errors";
import { isErrorObject } from "@/errors";
import { BusinessError, isErrorObject } from "@/errors";

const jqStacktraceRegexp = /jq: error \(at <stdin>:0\): (?<message>.*)/;

Expand Down Expand Up @@ -74,6 +74,11 @@ export class JQTransformer extends Transformer {
!isErrorObject(error) ||
(error.message.length > 13 && !error.message.includes("compile error"))
) {
// Unless there's bug in jq itself, if there's an error at this point, it's business error
if (isErrorObject(error)) {
throw new BusinessError(error.message, { cause: error });
}

throw error;
}

Expand Down
6 changes: 4 additions & 2 deletions src/errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
MultipleElementsFoundError,
NoElementsFoundError,
selectError,
serializePixiebrixError,
serializeErrorAndProperties,
} from "@/errors";
import { range } from "lodash";
import { deserializeError, serializeError } from "serialize-error";
Expand Down Expand Up @@ -306,8 +306,10 @@ describe("serialization", () => {
cause: inputValidationError,
});

const serializedError = serializePixiebrixError(contextError);
const serializedError = serializeErrorAndProperties(contextError);

// Use the isPlainObject from "@reduxjs/toolkit" because it's Redux that requires the object in the state to be
// serializable. We want it to be serializable and especially serializable for redux.
expect(isPlainObject(serializedError)).toBeTruthy();
expect(isPlainObject(serializedError.cause)).toBeTruthy();
});
Expand Down
9 changes: 5 additions & 4 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,12 @@ export function isContextError(error: unknown): error is ContextError {
);
}

// The serializeError preserves custom properties that effectively means the "cause" is skipped by the serializer
export function serializePixiebrixError(error: unknown): ErrorObject {
const serializedError = serializeError(error);
// `serialize-error/serializeError` preserves custom properties, so "cause" in our errors, e.g., ContextError is skipped
// https://github.com/sindresorhus/serialize-error/issues/74
export function serializeErrorAndProperties(error: unknown): ErrorObject {
const serializedError = serializeError(error, { useToJSON: false });
if (typeof error === "object" && "cause" in error) {
serializedError.cause = serializePixiebrixError(
serializedError.cause = serializeErrorAndProperties(
(error as { cause: unknown }).cause
);
}
Expand Down

0 comments on commit 1fadfdf

Please sign in to comment.