--- /dev/null
+import { ApolloError } from '@apollo/client/core';
+import { readable } from 'svelte/store';
+import { client } from '$lib/config/apollo';
+import type { DocumentNode, ApolloQueryResult } from '@apollo/client/core';
+
+import type { Readable } from 'svelte/store';
+
+/*
+ * This is a generic store for use with apollo
+ */
+
+type ApolloStoreConfiguration<Type> = {
+ key: string;
+ query: DocumentNode;
+ initialValue?: Type | void;
+ variables?: object;
+};
+
+type ApolloStoreState<Type> = {
+ loading: boolean;
+ data: Type | void;
+ error: Error | void;
+};
+
+export const store = function store<Type>({
+ key,
+ query,
+ initialValue = null,
+ variables = {}
+}: ApolloStoreConfiguration<Type>): Readable<ApolloStoreState<Type>> {
+ const initialState: ApolloStoreState<Type> = {
+ loading: true,
+ data: initialValue,
+ error: undefined
+ };
+
+ return readable(initialState, (set) => {
+ const handleError = function (error: Error) {
+ return set({
+ loading: false,
+ data: initialValue,
+ error
+ });
+ };
+
+ client.watchQuery({ query, variables }).subscribe(
+ (result: ApolloQueryResult<Type>) => {
+ if (result.errors) {
+ const error = new ApolloError({ graphQLErrors: result.errors });
+ return handleError(error);
+ }
+
+ set({
+ loading: false,
+ data: result.data[key],
+ error: undefined
+ });
+ },
+ (error: Error) => handleError(error)
+ );
+ });
+};
jest.mock('$lib/config/config.ts');
-import { getPost } from './posts';
+import { post } from './posts';
const internals = {
provider: null
};
-describe('Posts store pact', () => {
+describe('post', () => {
beforeAll(async () => {
internals.provider = new Pact({
port: 1234,
});
test('it returns the post', async () => {
- const post = getPost('8f75eba5-6989-4dd3-b466-e464546ce374');
+ const post = post('8f75eba5-6989-4dd3-b466-e464546ce374');
const { counter, promise: resolveAfterTwo } = resolveAfter(2);
let response = null;
post.subscribe((postValue) => {
});
test('it returns the post', async () => {
- const post = getPost('8f75eba5-6989-4dd3-b466-e464546ce374');
+ const post = post('8f75eba5-6989-4dd3-b466-e464546ce374');
const { counter, promise: resolveAfterTwo } = resolveAfter(2);
let response = null;
post.subscribe((postValue) => {
});
test('it returns the error', async () => {
- const post = getPost('8f75eba5-6989-4dd3-b466-e464546ce374');
+ const post = post('8f75eba5-6989-4dd3-b466-e464546ce374');
const { counter, promise: resolveAfterTwo } = resolveAfter(2);
let response = null;
post.subscribe((postValue) => {
});
test('it returns the error', async () => {
- const post = getPost('8f75eba5-6989-4dd3-b466-e464546ce374');
+ const post = post('8f75eba5-6989-4dd3-b466-e464546ce374');
const { counter, promise: resolveAfterTwo } = resolveAfter(2);
let response = null;
post.subscribe((postValue) => {
--- /dev/null
+import { GraphQLInteraction, Pact, Matchers } from '@pact-foundation/pact';
+import { resolve } from 'path';
+
+import { resolveAfter } from '$lib/utils/resolve_after';
+
+const { eachLike, like } = Matchers;
+
+jest.mock('$lib/config/config.ts');
+
+import { getTag } from './tags';
+
+const internals = {
+ provider: null
+};
+
+describe('Tags store pact', () => {
+ beforeAll(async () => {
+ internals.provider = new Pact({
+ port: 1234,
+ dir: resolve(process.cwd(), 'pacts'),
+ consumer: 'ForumClient',
+ provider: 'ForumServer',
+ pactfileWriteMode: 'update'
+ });
+
+ await internals.provider.setup();
+ });
+
+ afterEach(() => internals.provider.verify());
+ afterAll(() => internals.provider.finalize());
+
+ describe("When there's data", () => {
+ describe('GetTag', () => {
+ beforeAll(async () => {
+ const tagQuery = new GraphQLInteraction()
+ .given("there's data")
+ .uponReceiving('a request to get a single tag')
+ .withRequest({
+ path: '/graphql',
+ method: 'POST'
+ })
+ .withOperation('GetTag')
+ .withQuery(
+ `query GetTag($id: ID!) {
+ tag(id: $id) {
+ id
+ topics {
+ id
+ title
+ updated_at
+ ttl
+ __typename
+ }
+ __typename
+ }
+ }`
+ )
+ .withVariables({
+ id: 'pineapple'
+ })
+ .willRespondWith({
+ status: 200,
+ headers: {
+ 'Content-Type': 'application/json; charset=utf-8'
+ },
+ body: {
+ data: {
+ tag: {
+ id: like('pineapple'),
+ topics: eachLike({
+ id: like('cd038ae7-e8b4-4e38-9543-3d697e69ac34'),
+ title: like('This topic is about pineapples'),
+ updated_at: like(1619978944077),
+ ttl: like(3555)
+ })
+ }
+ }
+ }
+ });
+ return await internals.provider.addInteraction(tagQuery);
+ });
+
+ test('it returns the tag', async () => {
+ const tag = getTag('pineapple');
+ const { counter, promise: resolveAfterTwo } = resolveAfter(2);
+ let response = null;
+ tag.subscribe((tagValue) => {
+ response = tagValue;
+ counter();
+ });
+ expect(response.data).toBe(null);
+ expect(response.loading).toBe(true);
+ expect(response.error).toBe(undefined);
+ await resolveAfterTwo;
+ expect(response.data).toEqual({
+ id: 'pineapple',
+ topics: [
+ {
+ id: 'cd038ae7-e8b4-4e38-9543-3d697e69ac34',
+ title: 'This topic is about pineapples',
+ updated_at: 1619978944077,
+ ttl: 3555
+ }
+ ]
+ });
+ expect(response.loading).toBe(false);
+ expect(response.error).toBe(undefined);
+ });
+ });
+ });
+
+ describe("When there's no data", () => {
+ describe('GetTag', () => {
+ beforeAll(async () => {
+ const tagQuery = new GraphQLInteraction()
+ .given("there's no data")
+ .uponReceiving('a request to get a single tag')
+ .withRequest({
+ path: '/graphql',
+ method: 'POST'
+ })
+ .withOperation('GetTag')
+ .withQuery(
+ `query GetTag($id: ID!) {
+ tag(id: $id) {
+ id
+ topics {
+ id
+ title
+ updated_at
+ ttl
+ __typename
+ }
+ __typename
+ }
+ }`
+ )
+ .withVariables({
+ id: 'pineapple'
+ })
+ .willRespondWith({
+ status: 200,
+ headers: {
+ 'Content-Type': 'application/json; charset=utf-8'
+ },
+ body: {
+ data: {
+ tag: null
+ }
+ }
+ });
+ return await internals.provider.addInteraction(tagQuery);
+ });
+
+ test('it returns the tag', async () => {
+ const tag = getTag('pineapple');
+ const { counter, promise: resolveAfterTwo } = resolveAfter(2);
+ let response = null;
+ tag.subscribe((tagValue) => {
+ response = tagValue;
+ counter();
+ });
+ expect(response.data).toBe(null);
+ expect(response.loading).toBe(true);
+ expect(response.error).toBe(undefined);
+ await resolveAfterTwo;
+ expect(response.data).toBe(null);
+ expect(response.loading).toBe(false);
+ expect(response.error).toBe(undefined);
+ });
+ });
+ });
+
+ describe("When there's a server error", () => {
+ describe('GetTag', () => {
+ beforeAll(async () => {
+ const tagQuery = new GraphQLInteraction()
+ .given("there's a server error")
+ .uponReceiving('a request to get a single tag')
+ .withRequest({
+ path: '/graphql',
+ method: 'POST'
+ })
+ .withOperation('GetTag')
+ .withQuery(
+ `query GetTag($id: ID!) {
+ tag(id: $id) {
+ id
+ topics {
+ id
+ title
+ updated_at
+ ttl
+ __typename
+ }
+ __typename
+ }
+ }`
+ )
+ .withVariables({
+ id: 'pineapple'
+ })
+ .willRespondWith({
+ status: 500
+ });
+ return await internals.provider.addInteraction(tagQuery);
+ });
+
+ test('it returns the error', async () => {
+ const tag = getTag('pineapple');
+ const { counter, promise: resolveAfterTwo } = resolveAfter(2);
+ let response = null;
+ tag.subscribe((tagValue) => {
+ response = tagValue;
+ counter();
+ });
+ expect(response.data).toBe(null);
+ expect(response.loading).toBe(true);
+ expect(response.error).toBe(undefined);
+ await resolveAfterTwo;
+ expect(response.data).toBe(null);
+ expect(response.loading).toBe(false);
+ expect(response.error).toBeInstanceOf(Error);
+ });
+ });
+ });
+
+ describe("When there's an error in the response", () => {
+ describe('GetTag', () => {
+ beforeAll(async () => {
+ const tagQuery = new GraphQLInteraction()
+ .given("there's an error in the response")
+ .uponReceiving('a request to get a single tag')
+ .withRequest({
+ path: '/graphql',
+ method: 'POST'
+ })
+ .withOperation('GetTag')
+ .withQuery(
+ `query GetTag($id: ID!) {
+ tag(id: $id) {
+ id
+ topics {
+ id
+ title
+ updated_at
+ ttl
+ __typename
+ }
+ __typename
+ }
+ }`
+ )
+ .withVariables({
+ id: 'pineapple'
+ })
+ .willRespondWith({
+ status: 200,
+ headers: {
+ 'Content-Type': 'application/json; charset=utf-8'
+ },
+ body: {
+ errors: eachLike({
+ message: like('An error occurred when fetching the tag')
+ })
+ }
+ });
+ return await internals.provider.addInteraction(tagQuery);
+ });
+
+ test('it returns the error', async () => {
+ const tag = getTag('pineapple');
+ const { counter, promise: resolveAfterTwo } = resolveAfter(2);
+ let response = null;
+ tag.subscribe((tagValue) => {
+ response = tagValue;
+ counter();
+ });
+ expect(response.data).toBe(null);
+ expect(response.loading).toBe(true);
+ expect(response.error).toBe(undefined);
+ await resolveAfterTwo;
+ expect(response.data).toBe(null);
+ expect(response.loading).toBe(false);
+ expect(response.error.graphQLErrors).toEqual(
+ expect.arrayContaining([
+ {
+ message: 'An error occurred when fetching the tag'
+ }
+ ])
+ );
+ });
+ });
+ });
+});