Skip to content

Commit

Permalink
Add basic tests for persistedExchange
Browse files Browse the repository at this point in the history
  • Loading branch information
kitten committed Mar 15, 2023
1 parent 41b27c2 commit 74e3d09
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 29 deletions.
115 changes: 115 additions & 0 deletions exchanges/persisted/src/persistedExchange.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {
Source,
pipe,
fromValue,
fromArray,
toPromise,
delay,
take,
tap,
map,
} from 'wonka';

import { Client, Operation, OperationResult, CombinedError } from '@urql/core';

import { vi, expect, it } from 'vitest';
import { queryOperation } from '../../../packages/core/src/test-utils';
import { persistedExchange } from './persistedExchange';

const makeExchangeArgs = () => {
const operations: Operation[] = [];

const result = vi.fn(
(operation: Operation): OperationResult => ({ operation })
);

return {
operations,
result,
exchangeArgs: {
forward: (op$: Source<Operation>) =>
pipe(
op$,
tap(op => operations.push(op)),
map(result)
),
client: new Client({ url: '/api' }),
} as any,
};
};

it('adds the APQ extensions correctly', async () => {
const { exchangeArgs } = makeExchangeArgs();

const res = await pipe(
fromValue(queryOperation),
persistedExchange()(exchangeArgs),
take(1),
toPromise
);

expect(res.operation.context.persistAttempt).toBe(true);
expect(res.operation.extensions).toEqual({
persistedQuery: {
version: 1,
sha256Hash: expect.any(String),
miss: undefined,
},
});
});

it('retries query when persisted query resulted in miss', async () => {
const { result, operations, exchangeArgs } = makeExchangeArgs();

result.mockImplementationOnce(operation => ({
operation,
error: new CombinedError({
graphQLErrors: [{ message: 'PersistedQueryNotFound' }],
}),
}));

const res = await pipe(
fromValue(queryOperation),
persistedExchange()(exchangeArgs),
take(1),
toPromise
);

expect(res.operation.context.persistAttempt).toBe(true);
expect(operations.length).toBe(2);

expect(operations[1].extensions).toEqual({
persistedQuery: {
version: 1,
sha256Hash: expect.any(String),
miss: true,
},
});
});

it('retries query persisted query resulted in unsupported', async () => {
const { result, operations, exchangeArgs } = makeExchangeArgs();

result.mockImplementationOnce(operation => ({
operation,
error: new CombinedError({
graphQLErrors: [{ message: 'PersistedQueryNotSupported' }],
}),
}));

await pipe(
fromArray([queryOperation, queryOperation]),
delay(0),
persistedExchange()(exchangeArgs),
take(2),
toPromise
);

expect(operations.length).toBe(3);

expect(operations[1].extensions).toEqual({
persistedQuery: undefined,
});

expect(operations[2].extensions).toEqual(undefined);
});
58 changes: 30 additions & 28 deletions exchanges/persisted/src/persistedExchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ export const persistedExchange = (
): Exchange => ({ forward }) => {
if (!options) options = {};

const retries = makeSubject<Operation>();

const preferGetForPersistedQueries = !!options.preferGetForPersistedQueries;
const enforcePersistedQueries = !!options.enforcePersistedQueries;
const hashFn = options.generateHash || hash;
Expand All @@ -56,6 +54,7 @@ export const persistedExchange = (
operation.kind === 'query');

return operations$ => {
const retries = makeSubject<Operation>();
const sharedOps$ = share(operations$);

const forwardedOps$ = pipe(
Expand All @@ -66,43 +65,46 @@ export const persistedExchange = (
const persistedOps$ = pipe(
sharedOps$,
filter(operationFilter),
mergeMap(operation => {
map(async operation => {
const persistedOperation = makeOperation(operation.kind, operation, {
...operation.context,
persistAttempt: true,
});

return pipe(
fromPromise(
hashFn(stringifyDocument(operation.query), operation.query)
),
map(sha256Hash => {
if (sha256Hash) {
persistedOperation.extensions = {
...persistedOperation.extensions,
persistedQuery: {
version: 1,
sha256Hash,
},
};
if (
persistedOperation.kind === 'query' &&
preferGetForPersistedQueries
) {
persistedOperation.context.preferGetMethod = 'force';
}
}
return persistedOperation;
})
const sha256Hash = await hashFn(
stringifyDocument(operation.query),
operation.query
);
})
if (sha256Hash) {
persistedOperation.extensions = {
...persistedOperation.extensions,
persistedQuery: {
version: 1,
sha256Hash,
},
};
if (
persistedOperation.kind === 'query' &&
preferGetForPersistedQueries
) {
persistedOperation.context.preferGetMethod = 'force';
}
}

return persistedOperation;
}),
mergeMap(fromPromise)
);

return pipe(
merge([forwardedOps$, persistedOps$, retries.source]),
merge([persistedOps$, forwardedOps$, retries.source]),
forward,
map(result => {
if (!enforcePersistedQueries) {
if (
!enforcePersistedQueries &&
result.operation.extensions &&
result.operation.extensions.persistedQuery
) {
if (result.error && isPersistedUnsupported(result.error)) {
// Disable future persisted queries if they're not enforced
supportsPersistedQueries = false;
Expand Down
2 changes: 1 addition & 1 deletion exchanges/persisted/src/sha256.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const getNodeCrypto = async (): Promise<typeof import('crypto') | void> => {
};

export const hash = async (query: string): Promise<string> => {
if (webCrypto) {
if (webCrypto && webCrypto.subtle) {
const digest = await webCrypto.subtle.digest(
{ name: 'SHA-256' },
new TextEncoder().encode(query)
Expand Down

0 comments on commit 74e3d09

Please sign in to comment.