diff --git a/src/graphql.ts b/src/graphql.ts index 40ed44da..81bed1d5 100644 --- a/src/graphql.ts +++ b/src/graphql.ts @@ -2,24 +2,69 @@ import AltairFastify from "altair-fastify-plugin"; import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; import mercurius from "mercurius"; import mercuriusCodegen from "mercurius-codegen"; -import { makeSchema, queryType, stringArg } from "nexus"; +import { + makeSchema, + queryType, + stringArg, + objectType, + idArg, + inputObjectType, + mutationType, +} from "nexus"; const buildContext = async (req: FastifyRequest, _reply: FastifyReply) => ({ authorization: req.headers.authorization, }); // eslint-disable-next-line @typescript-eslint/naming-convention -const Query = queryType({ +const User = objectType({ + name: "User", + definition(t) { + t.id("id"); + t.string("name"); + }, +}); + +const userInput = inputObjectType({ + name: "UserInput", + definition(t) { + t.nonNull.string("name"); + }, +}); + +const query = queryType({ definition(t) { t.string("hello", { args: { name: stringArg() }, resolve: (_parent, { name }) => `Hello ${name ?? "World"}!`, }); + + t.field("user", { + type: "User", + args: { id: idArg() }, + resolve(_parent, { id }) { + // NOTE: Implement user fetching logic + return null; + }, + }); + }, +}); + +const mutation = mutationType({ + definition(t) { + t.field("createUser", { + type: "User", + args: { input: "UserInput" }, + resolve(_parent, { input }) { + // NOTE: Implement user creation logic + return null; + }, + }); }, }); const schema = makeSchema({ - types: [Query], + types: [query, mutation, User, userInput], outputs: { schema: `${__dirname}/generated/schema.graphql`, typegen: `${__dirname}/generated/typings.ts`, @@ -39,9 +84,9 @@ export async function initGraphql(app: FastifyInstance) { await app.register(AltairFastify, { path: "/altair", - baseURL: "/altair/", - // 'endpointURL' should be the same as the mercurius 'path' - endpointURL: "/graphql", + baseUrl: "/altair/", + // 'endpointUrl' should be the same as the mercurius 'path' + endpointUrl: "/graphql", }); await mercuriusCodegen(app, { diff --git a/test/graphql/hello.test.ts b/test/graphql/hello.test.ts index 51a36e94..f4467d7e 100644 --- a/test/graphql/hello.test.ts +++ b/test/graphql/hello.test.ts @@ -11,7 +11,7 @@ void test("hello query", async (t) => { const response = await client.query( `query { hello - }` + }`, ); t.same(response, { @@ -20,3 +20,122 @@ void test("hello query", async (t) => { }, }); }); +void test("user query", async (t) => { + t.plan(4); + + const client = createMercuriusTestClient(app); + + // Simulate fetching a user by ID from a data source + const response = await client.query( + `query { + user(id: "1") { + id + name + } + }`, + ); + + t.same(response, { + data: { + user: { + id: "1", + name: "John Doe", + }, + }, + }); + + // Test for invalid ID + const invalidIdResponse = await client.query( + `query { + user(id: "invalid") { + id + name + } + }`, + ); + + t.same(invalidIdResponse, { + errors: [ + { + message: "Invalid ID", + }, + ], + }); + + // Test for nonexistent ID + const nonexistentIdResponse = await client.query( + `query { + user(id: "nonexistent") { + id + name + } + }`, + ); + + t.same(nonexistentIdResponse, { + data: { + user: null, + }, + }); +}); + +void test("createUser mutation", async (t) => { + t.plan(1); + + const client = createMercuriusTestClient(app); + + const response = await client.mutate( + `mutation { + createUser(input: {name: "Jane Doe"}) { + id + name + } + }`, + ); + + t.same(response, { + data: { + createUser: { + id: "2", + name: "Jane Doe", + }, + }, + }); +}); + + // Test for missing required input fields + const missingFieldsResponse = await client.mutate( + `mutation { + createUser(input: {}) { + id + name + } + }`, + ); + + t.same(missingFieldsResponse, { + errors: [ + { + message: "Missing required input fields", + }, + ], + }); + + // Test for invalid input fields + const invalidFieldsResponse = await client.mutate( + `mutation { + createUser(input: {name: 123}) { + id + name + } + }`, + ); + + t.same(invalidFieldsResponse, { + errors: [ + { + message: "Invalid input fields", + }, + ], + }); +}); diff --git a/test/routes/example.test.ts b/test/routes/example.test.ts index a4463d2d..5dd415fa 100644 --- a/test/routes/example.test.ts +++ b/test/routes/example.test.ts @@ -1,5 +1,4 @@ import { test } from "tap"; - import { build } from "../helper"; void test("example is loaded", async (t) => { @@ -9,5 +8,53 @@ void test("example is loaded", async (t) => { url: "/example", }); - t.equal(res.payload, '{"hello":"world"}'); + t.equal(res.statusCode, 200); + t.same(JSON.parse(res.payload), { hello: "world" }); +}); + +void test("example route - invalid method", async (t) => { + const app = await build(t); + + const res = await app.inject({ + method: "PUT", + url: "/example", + }); + + t.equal(res.statusCode, 405); +}); + +void test("example route - missing required query parameter", async (t) => { + const app = await build(t); + + const res = await app.inject({ + method: "GET", + url: "/example?realParam=", + }); + + t.equal(res.statusCode, 400); +}); + +void test("example route - invalid query parameter value", async (t) => { + const app = await build(t); + + const res = await app.inject({ + method: "GET", + url: "/example?param=invalidValue", + }); + + t.equal(res.statusCode, 400); +}); + +void test("example route - middleware", async (t) => { + const app = await build(t); + + const res = await app.inject({ + method: "GET", + url: "/example", + headers: { + "X-Custom-Header": "invalid", + }, + }); + + t.equal(res.statusCode, 400); }); diff --git a/test/routes/root.test.ts b/test/routes/root.test.ts index f1f10622..9582d44f 100644 --- a/test/routes/root.test.ts +++ b/test/routes/root.test.ts @@ -2,11 +2,66 @@ import { test } from "tap"; import { build } from "../helper"; -void test("default root route", async (t) => { +void test("default root route - GET", async (t) => { const app = await build(t); const res = await app.inject({ + method: "GET", url: "/", }); t.same(JSON.parse(res.payload), { status: true }); }); + +void test("default root route - POST", async (t) => { + const app = await build(t); + + const res = await app.inject({ + method: "POST", + url: "/", + payload: { data: "test" }, + }); + // Assuming the POST request returns the same data received in the payload + t.same(JSON.parse(res.payload), { data: "test" }); +}); + +void test("default root route - invalid method", async (t) => { + const app = await build(t); + + const res = await app.inject({ + method: "PUT", + url: "/", + }); + t.equal(res.statusCode, 405); +}); +void test("default root route - missing required query parameter", async (t) => { + const app = await build(t); + + const res = await app.inject({ + method: "GET", + url: "/?missingParam=value", + }); + t.equal(res.statusCode, 400); +}); + +void test("default root route - invalid query parameter value", async (t) => { + const app = await build(t); + + const res = await app.inject({ + method: "GET", + url: "/?param=invalidValue", + }); + t.equal(res.statusCode, 400); +}); + +void test("default root route - middleware", async (t) => { + const app = await build(t); + + const res = await app.inject({ + method: "GET", + url: "/", + headers: { + "X-Custom-Header": "invalid", + }, + }); + t.equal(res.statusCode, 400); +});