-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(core): add general builder and cli-fixture tests (#96)
* add general builder tests * add node resolution * add example fixture test * add build test * update gitignore * update vitest to v1 * remove cjs warning
- Loading branch information
1 parent
2b4073e
commit 59979fc
Showing
17 changed files
with
705 additions
and
134 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'fuse': patch | ||
--- | ||
|
||
Remove log from list-plugin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
node_modules | ||
dist | ||
packages/core/test/fixtures/**/build | ||
packages/core/test/fixtures/**/schema.graphql |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -82,6 +82,8 @@ prog | |
}), | ||
], | ||
}) | ||
|
||
console.log('Server build output created in ./build') | ||
} | ||
|
||
if (opts.client) { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import path from 'node:path' | ||
import fs, { existsSync } from 'node:fs' | ||
import { afterAll, beforeAll, describe, expect, test } from 'vitest' | ||
import { ExecaChildProcess, execa } from 'execa' | ||
import { afterEach } from 'node:test' | ||
|
||
const fixturesDir = path.join(__dirname, 'fixtures') | ||
const allFixtures = fs.readdirSync(fixturesDir) | ||
|
||
const wait = () => { | ||
return new Promise((resolve) => { | ||
setTimeout(() => { | ||
resolve(null) | ||
}, 500) | ||
}) | ||
} | ||
|
||
describe.each(allFixtures)('%s', (fixtureName) => { | ||
const fixtureDir = path.join(fixturesDir, fixtureName) | ||
let process: ExecaChildProcess<string> | undefined | ||
|
||
beforeAll(async () => { | ||
await execa('pnpm', ['install'], { cwd: fixtureDir }) | ||
}, 25_000) | ||
|
||
afterAll(async () => { | ||
await fs.promises.rm(path.join(fixtureDir, 'node_modules'), { | ||
recursive: true, | ||
}) | ||
await fs.promises.rm(path.join(fixtureDir, 'build'), { | ||
recursive: true, | ||
}) | ||
await fs.promises.rm(path.join(fixtureDir, 'schema.graphql')) | ||
}, 25_000) | ||
|
||
afterEach(async () => { | ||
if (process) { | ||
process.kill('SIGTERM') | ||
await wait() | ||
process = undefined | ||
} | ||
}) | ||
|
||
test('Should run the dev command', async () => { | ||
process = execa('pnpm', ['fuse', 'dev', '--server'], { | ||
cwd: fixtureDir, | ||
}) | ||
|
||
await new Promise((resolve) => { | ||
process!.stdout?.on('data', (data) => { | ||
const msg = data.toString() | ||
if (msg.includes('Server listening on')) { | ||
resolve(null) | ||
} | ||
}) | ||
}) | ||
|
||
const result = await fetch('http://localhost:4000/graphql', { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify({ | ||
query: '{ _version }', | ||
}), | ||
}).then((x) => x.json()) | ||
expect(result.data._version).toBeDefined() | ||
}, 10_000) | ||
|
||
test('Should run the build command', async () => { | ||
process = execa('pnpm', ['fuse', 'build', '--server'], { | ||
cwd: fixtureDir, | ||
}) | ||
|
||
await new Promise((resolve) => { | ||
process!.stdout?.on('data', (data) => { | ||
const msg = data.toString() | ||
if (msg.includes('Server build output created')) { | ||
resolve(null) | ||
} | ||
}) | ||
}) | ||
|
||
expect(existsSync(path.join(fixtureDir, 'build'))).toBe(true) | ||
|
||
process = execa('node', ['./build/node.js'], { | ||
cwd: fixtureDir, | ||
env: { | ||
NODE_ENV: 'production', | ||
PORT: '3000', | ||
}, | ||
}) | ||
|
||
// Our built node output does not log its start | ||
await wait() | ||
|
||
const result = await fetch('http://localhost:3000/graphql', { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify({ | ||
query: '{ _version }', | ||
}), | ||
}).then((x) => x.json()) | ||
expect(result.data._version).toBeDefined() | ||
}, 10_000) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import { test, expect } from 'vitest' | ||
import { execute, parse } from 'graphql' | ||
|
||
let counter = 0 | ||
const importFuse = () => { | ||
return import('../src/builder?test-errors=' + counter++) | ||
} | ||
|
||
test('Should correctly format an authentication error', async () => { | ||
const mod = await importFuse() | ||
const { builder, addQueryFields, AuthenticationError } = mod | ||
|
||
addQueryFields((t) => ({ | ||
authn: t.field({ | ||
type: 'String', | ||
description: 'An authentication check', | ||
resolve: () => { | ||
throw new AuthenticationError('You are not authenticated') | ||
}, | ||
}), | ||
})) | ||
|
||
const schema = builder.toSchema() | ||
|
||
const document = parse(`query { authn }`) | ||
const result = (await execute({ | ||
document, | ||
schema, | ||
contextValue: {}, | ||
})) as { data?: any; errors?: any } | ||
|
||
expect(result.data).toEqual({ authn: null }) | ||
expect(result.errors).toBeDefined() | ||
expect(result.errors).toHaveLength(1) | ||
expect(result.errors[0].message).toEqual('You are not authenticated') | ||
expect(result.errors[0].extensions).toEqual({ | ||
code: 'UNAUTHENTICATED', | ||
}) | ||
}) | ||
|
||
test('Should correctly format an authorization error', async () => { | ||
const mod = await importFuse() | ||
const { builder, addQueryFields, ForbiddenError } = mod | ||
|
||
addQueryFields((t) => ({ | ||
authz: t.field({ | ||
type: 'String', | ||
description: 'An authorization check', | ||
resolve: () => { | ||
throw new ForbiddenError('You are not authorized') | ||
}, | ||
}), | ||
})) | ||
|
||
const schema = builder.toSchema() | ||
|
||
const document = parse(`query { authz }`) | ||
const result = (await execute({ | ||
document, | ||
schema, | ||
contextValue: {}, | ||
})) as { data?: any; errors?: any } | ||
|
||
expect(result.data).toEqual({ authz: null }) | ||
expect(result.errors).toBeDefined() | ||
expect(result.errors).toHaveLength(1) | ||
expect(result.errors[0].message).toEqual('You are not authorized') | ||
expect(result.errors[0].extensions).toEqual({ | ||
code: 'FORBIDDEN', | ||
}) | ||
}) | ||
|
||
test('Should correctly format a not-found error', async () => { | ||
const mod = await importFuse() | ||
const { builder, addQueryFields, NotFoundError } = mod | ||
|
||
addQueryFields((t) => ({ | ||
notFound: t.field({ | ||
type: 'String', | ||
description: 'An authorization check', | ||
resolve: () => { | ||
throw new NotFoundError('Entity cannot be found') | ||
}, | ||
}), | ||
})) | ||
|
||
const schema = builder.toSchema() | ||
|
||
const document = parse(`query { notFound }`) | ||
const result = (await execute({ | ||
document, | ||
schema, | ||
contextValue: {}, | ||
})) as { data?: any; errors?: any } | ||
|
||
expect(result.data).toEqual({ notFound: null }) | ||
expect(result.errors).toBeDefined() | ||
expect(result.errors).toHaveLength(1) | ||
expect(result.errors[0].message).toEqual('Entity cannot be found') | ||
expect(result.errors[0].extensions).toEqual({ | ||
code: 'NOT_FOUND', | ||
}) | ||
}) | ||
|
||
test('Should correctly format an authorization error', async () => { | ||
const mod = await importFuse() | ||
const { builder, addQueryFields, ForbiddenError } = mod | ||
|
||
addQueryFields((t) => ({ | ||
authz: t.field({ | ||
type: 'String', | ||
description: 'A not-found check', | ||
resolve: () => { | ||
throw new ForbiddenError('You are not authorized') | ||
}, | ||
}), | ||
})) | ||
|
||
const schema = builder.toSchema() | ||
|
||
const document = parse(`query { authz }`) | ||
const result = (await execute({ | ||
document, | ||
schema, | ||
contextValue: {}, | ||
})) as { data?: any; errors?: any } | ||
|
||
expect(result.data).toEqual({ authz: null }) | ||
expect(result.errors).toBeDefined() | ||
expect(result.errors).toHaveLength(1) | ||
expect(result.errors[0].message).toEqual('You are not authorized') | ||
expect(result.errors[0].extensions).toEqual({ | ||
code: 'FORBIDDEN', | ||
}) | ||
}) | ||
|
||
test('Should correctly format a bad-request error', async () => { | ||
const mod = await importFuse() | ||
const { builder, addQueryFields, BadRequestError } = mod | ||
|
||
addQueryFields((t) => ({ | ||
badRequest: t.field({ | ||
type: 'String', | ||
description: 'A bad-request check', | ||
resolve: () => { | ||
throw new BadRequestError('Missing id') | ||
}, | ||
}), | ||
})) | ||
|
||
const schema = builder.toSchema() | ||
|
||
const document = parse(`query { badRequest }`) | ||
const result = (await execute({ | ||
document, | ||
schema, | ||
contextValue: {}, | ||
})) as { data?: any; errors?: any } | ||
|
||
expect(result.data).toEqual({ badRequest: null }) | ||
expect(result.errors).toBeDefined() | ||
expect(result.errors).toHaveLength(1) | ||
expect(result.errors[0].message).toEqual('Missing id') | ||
expect(result.errors[0].extensions).toEqual({ | ||
code: 'BAD_REQUEST', | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"name": "@fuse-fixtures/simple", | ||
"private": true, | ||
"version": "0.0.0", | ||
"type": "module", | ||
"devDependencies": { | ||
"fuse": "workspace:*", | ||
"typescript": "^5.2.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "ES2020", | ||
"useDefineForClassFields": true, | ||
"lib": ["ES2020"], | ||
"module": "ESNext", | ||
"skipLibCheck": true, | ||
|
||
/* Bundler mode */ | ||
"moduleResolution": "bundler", | ||
"allowImportingTsExtensions": true, | ||
"resolveJsonModule": true, | ||
"isolatedModules": true, | ||
"noEmit": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { node } from 'fuse' | ||
|
||
type UserSource = { | ||
id: string | ||
name: string | ||
avatar_url: string | ||
} | ||
|
||
// "Nodes" are the core abstraction of Fuse.js. Each node represents | ||
// a resource/entity with multiple fields and has to define two things: | ||
// 1. load(): How to fetch from the underlying data source | ||
// 2. fields: What fields should be exposed and added for clients | ||
export const UserNode = node<UserSource>({ | ||
name: 'User', | ||
load: async (ids) => getUsers(ids), | ||
fields: (t) => ({ | ||
name: t.exposeString('name'), | ||
// rename to camel-case | ||
avatarUrl: t.exposeString('avatar_url'), | ||
// Add an additional firstName field | ||
firstName: t.string({ | ||
resolve: (user) => user.name.split(' ')[0], | ||
}), | ||
}), | ||
}) | ||
|
||
// Fake function to fetch users. In real applications, this would | ||
// talk to an underlying REST API/gRPC service/third-party API/… | ||
async function getUsers(ids: string[]): Promise<UserSource[]> { | ||
return ids.map((id) => ({ | ||
id, | ||
name: `Peter #${id}`, | ||
avatar_url: `https://i.pravatar.cc/300?u=${id}`, | ||
})) | ||
} |
Oops, something went wrong.
59979fc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
spacex-fuse – ./examples/spacex
spacex-fuse-stellate.vercel.app
spacex-fuse-git-main-stellate.vercel.app
spacex-fuse.vercel.app