Skip to content

Commit

Permalink
chore: adds tests and prevents undefined message on error
Browse files Browse the repository at this point in the history
  • Loading branch information
alessbell committed Mar 29, 2023
1 parent 52a9c8e commit 7b13cde
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 15 deletions.
5 changes: 1 addition & 4 deletions src/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ const generateErrorMessage = (err: ApolloError) => {
const errors = ((err.graphQLErrors || []) as readonly Error[])
.concat(err.clientErrors || []);
errors.forEach((error: Error) => {
const errorMessage = error
? error.message
: 'Error message not found.';
message += `${errorMessage}\n`;
message += `${error.message || error}\n`;
});
}

Expand Down
72 changes: 71 additions & 1 deletion src/link/subscriptions/__tests__/graphqlWsLink.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Client } from "graphql-ws";
import { ExecutionResult } from "graphql";
import { ExecutionResult, GraphQLError } from "graphql";
import gql from "graphql-tag";

import { Observable } from "../../../utilities";
import { ApolloError } from "../../../errors";
import { execute } from "../../core";
import { GraphQLWsLink } from "..";

Expand Down Expand Up @@ -104,4 +105,73 @@ describe("GraphQLWSlink", () => {
const obs = execute(link, { query: subscription });
await expect(observableToArray(obs)).resolves.toEqual(results);
});

describe("should reject", () => {
it("with Error on subscription error via Error", async () => {
const subscribe: Client["subscribe"] = (_, sink) => {
sink.error(new Error("an error occurred"));
return () => {};
};
const client = mockClient(subscribe);
const link = new GraphQLWsLink(client);

const obs = execute(link, { query: subscription });
await expect(observableToArray(obs)).rejects.toEqual(
new Error("an error occurred")
);
});

it("with Error on subscription error via CloseEvent", async () => {
const subscribe: Client["subscribe"] = (_, sink) => {
// A WebSocket close event receives a CloseEvent
// See: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close_event
sink.error(
new CloseEvent("an error occurred", {
code: 1006,
reason: "abnormally closed",
})
);
return () => {};
};
const client = mockClient(subscribe);
const link = new GraphQLWsLink(client);

const obs = execute(link, { query: subscription });
await expect(observableToArray(obs)).rejects.toEqual(
new Error("Socket closed with event 1006 abnormally closed")
);
});

it("with ApolloError on subscription error via Event (network disconnected)", async () => {
const subscribe: Client["subscribe"] = (_, sink) => {
// A WebSocket error event receives a generic Event
// See: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/error_event
sink.error({ target: { readyState: WebSocket.CLOSED } });
return () => {};
};
const client = mockClient(subscribe);
const link = new GraphQLWsLink(client);

const obs = execute(link, { query: subscription });
await expect(observableToArray(obs)).rejects.toEqual(
new Error("Socket closed with event 3")
);
});

it("with ApolloError on subscription error via GraphQLError[]", async () => {
const subscribe: Client["subscribe"] = (_, sink) => {
sink.error([new GraphQLError("Foo bar.")]);
return () => {};
};
const client = mockClient(subscribe);
const link = new GraphQLWsLink(client);

const obs = execute(link, { query: subscription });
await expect(observableToArray(obs)).rejects.toEqual(
new ApolloError({
graphQLErrors: [new GraphQLError("Foo bar.")],
})
);
});
});
});
29 changes: 19 additions & 10 deletions src/link/subscriptions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,16 @@ import { ApolloLink, Operation, FetchResult } from "../core";
import { isNonNullObject, Observable } from "../../utilities";
import { ApolloError } from "../../errors";

interface LikeCloseEvent {
/** Returns the WebSocket connection close code provided by the server. */
readonly code: number;
/** Returns the WebSocket connection close reason provided by the server. */
readonly reason: string;
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close_event
function isLikeCloseEvent(val: unknown): val is CloseEvent {
return isNonNullObject(val) && "code" in val && "reason" in val;
}

function isLikeCloseEvent(val: unknown): val is LikeCloseEvent {
return isNonNullObject(val) && 'code' in val && 'reason' in val;
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/error_event
function isLikeErrorEvent(err: unknown): err is Event {
return isNonNullObject(err) && err.target?.readyState === WebSocket.CLOSED;
}


export class GraphQLWsLink extends ApolloLink {
constructor(public readonly client: Client) {
super();
Expand All @@ -63,12 +61,23 @@ export class GraphQLWsLink extends ApolloLink {
if (err instanceof Error) {
return observer.error(err);
}
if (isLikeCloseEvent(err) || isLikeErrorEvent(err)) {
let reason;
let code;

if (isLikeCloseEvent(err)) {
if (isLikeCloseEvent(err)) {
reason = err.reason;
code = err.code;
}
if (isLikeErrorEvent(err)) {
code = WebSocket.CLOSED;
}
return observer.error(
// reason will be available on clean closes
new Error(
`Socket closed with event ${err.code} ${err.reason || ""}`
`Socket closed with event ${code}${
reason ? ` ${reason}` : ""
}`
)
);
}
Expand Down

0 comments on commit 7b13cde

Please sign in to comment.