Skip to content

Commit f8bccc7

Browse files
authored
Merge pull request #71 from batjko/add-book-tests
Add comprehensive tests for books functionality
2 parents 2e0a205 + 9c05bae commit f8bccc7

File tree

3 files changed

+250
-29
lines changed

3 files changed

+250
-29
lines changed

.github/workflows/codeql-analysis.yml

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,33 +39,17 @@ jobs:
3939

4040
steps:
4141
- name: Checkout repository
42-
uses: actions/checkout@v3
42+
uses: actions/checkout@v4
4343

4444
# Initializes the CodeQL tools for scanning.
4545
- name: Initialize CodeQL
46-
uses: github/codeql-action/init@v2
46+
uses: github/codeql-action/init@v3
4747
with:
4848
languages: ${{ matrix.language }}
49-
# If you wish to specify custom queries, you can do so here or in a config file.
50-
# By default, queries listed here will override any specified in a config file.
51-
# Prefix the list here with "+" to use these queries and those in the config file.
52-
# queries: ./path/to/local/query, your-org/your-repo/queries@main
5349

5450
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
55-
# If this step fails, then you should remove it and run the build manually (see below)
5651
- name: Autobuild
57-
uses: github/codeql-action/autobuild@v2
58-
59-
# ℹ️ Command-line programs to run using the OS shell.
60-
# 📚 https://git.io/JvXDl
61-
62-
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
63-
# and modify them (or add more) to build your code if your project
64-
# uses a compiled language
65-
66-
#- run: |
67-
# make bootstrap
68-
# make release
52+
uses: github/codeql-action/autobuild@v3
6953

7054
- name: Perform CodeQL Analysis
71-
uses: github/codeql-action/analyze@v2
55+
uses: github/codeql-action/analyze@v3

.github/workflows/node.js.yml

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,23 @@
44
name: Run tests
55

66
on: [push, pull_request]
7-
# push:
8-
# branches: [ master ]
9-
# pull_request:
10-
# branches: [ master ]
117

128
jobs:
139
test:
1410
runs-on: ubuntu-latest
1511

1612
strategy:
1713
matrix:
18-
node-version: [15.x]
19-
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
14+
node-version: [22.x]
15+
# Node 20+ required by Apollo Server 5 and Prisma 7
2016

2117
steps:
22-
- uses: actions/checkout@v3
18+
- uses: actions/checkout@v4
2319
- name: Use Node.js ${{ matrix.node-version }}
24-
uses: actions/setup-node@v3
20+
uses: actions/setup-node@v4
2521
with:
2622
node-version: ${{ matrix.node-version }}
23+
cache: 'npm'
2724
- run: npm ci
28-
# - run: npm run build --if-present
25+
- run: npx prisma generate
2926
- run: npm test
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import test from 'ava'
2+
import gql from 'graphql-tag'
3+
4+
import { server } from '../../server.js'
5+
import { bookResolvers } from './index.js'
6+
import { client } from '../../providers/booksDB/dbClient.js'
7+
8+
// Helper to extract result from Apollo Server 5 response
9+
const getResult = (response) => {
10+
if (response.body.kind !== 'single') {
11+
throw new Error('Expected single result')
12+
}
13+
return response.body.singleResult
14+
}
15+
16+
// Helper to create a test book via mutation
17+
const createTestBook = async (title = `Test Book ${Date.now()}`, author = 'Test Author') => {
18+
const mutation = gql`
19+
mutation AddBook($title: String, $author: String) {
20+
addBook(title: $title, author: $author) {
21+
id
22+
title
23+
author
24+
}
25+
}
26+
`
27+
const response = await server.executeOperation({
28+
query: mutation,
29+
variables: { title, author }
30+
})
31+
return getResult(response).data.addBook
32+
}
33+
34+
// Clean up after all tests
35+
test.after.always(async () => {
36+
await client.$disconnect()
37+
})
38+
39+
// ============================================
40+
// Resolver structure tests
41+
// ============================================
42+
43+
test('Book resolvers include all expected functions', t => {
44+
t.truthy(bookResolvers?.Query?.books, 'books resolver missing')
45+
t.true(typeof bookResolvers.Query.books === 'function', 'books resolver is not a function')
46+
47+
t.truthy(bookResolvers?.Query?.book, 'book resolver missing')
48+
t.true(typeof bookResolvers.Query.book === 'function', 'book resolver is not a function')
49+
50+
t.truthy(bookResolvers?.Mutation?.addBook, 'addBook resolver missing')
51+
t.true(typeof bookResolvers.Mutation.addBook === 'function', 'addBook resolver is not a function')
52+
})
53+
54+
// ============================================
55+
// Query tests
56+
// ============================================
57+
58+
test('books query returns an array', async t => {
59+
const query = gql`
60+
query {
61+
books {
62+
id
63+
title
64+
author
65+
}
66+
}
67+
`
68+
const response = await server.executeOperation({ query })
69+
const result = getResult(response)
70+
71+
t.falsy(result.errors, 'books query returned errors')
72+
t.true(Array.isArray(result.data?.books), 'books should return an array')
73+
})
74+
75+
test('books query returns books with correct structure', async t => {
76+
// Ensure at least one book exists
77+
await createTestBook('Structure Test Book', 'Structure Author')
78+
79+
const query = gql`
80+
query {
81+
books {
82+
id
83+
title
84+
author
85+
}
86+
}
87+
`
88+
const response = await server.executeOperation({ query })
89+
const result = getResult(response)
90+
91+
t.falsy(result.errors)
92+
t.true(result.data.books.length >= 1, 'Should have at least one book')
93+
94+
const book = result.data.books[0]
95+
t.truthy(book.id, 'Book should have an id')
96+
t.truthy(book.title, 'Book should have a title')
97+
t.truthy(book.author, 'Book should have an author')
98+
})
99+
100+
test('book query returns a single book by id', async t => {
101+
// Create a book to query
102+
const createdBook = await createTestBook('Single Query Test', 'Query Author')
103+
const bookId = createdBook.id
104+
105+
const query = gql`
106+
query GetBook($id: ID!) {
107+
book(id: $id) {
108+
id
109+
title
110+
author
111+
}
112+
}
113+
`
114+
const response = await server.executeOperation({
115+
query,
116+
variables: { id: bookId }
117+
})
118+
const result = getResult(response)
119+
120+
t.falsy(result.errors, 'book query returned errors')
121+
t.truthy(result.data?.book, 'book query should return a book')
122+
t.is(result.data.book.id, bookId, 'Returned book should have the requested id')
123+
t.is(result.data.book.title, 'Single Query Test')
124+
t.is(result.data.book.author, 'Query Author')
125+
})
126+
127+
test('book query returns null for non-existent id', async t => {
128+
const query = gql`
129+
query GetBook($id: ID!) {
130+
book(id: $id) {
131+
id
132+
title
133+
author
134+
}
135+
}
136+
`
137+
const response = await server.executeOperation({
138+
query,
139+
variables: { id: '99999' }
140+
})
141+
const result = getResult(response)
142+
143+
t.falsy(result.errors, 'Should not return errors for non-existent book')
144+
t.is(result.data?.book, null, 'Should return null for non-existent book')
145+
})
146+
147+
// ============================================
148+
// Mutation tests
149+
// ============================================
150+
151+
test('addBook mutation creates a new book', async t => {
152+
const mutation = gql`
153+
mutation AddBook($title: String, $author: String) {
154+
addBook(title: $title, author: $author) {
155+
id
156+
title
157+
author
158+
}
159+
}
160+
`
161+
const testTitle = `Test Book ${Date.now()}`
162+
const testAuthor = 'Test Author'
163+
164+
const response = await server.executeOperation({
165+
query: mutation,
166+
variables: { title: testTitle, author: testAuthor }
167+
})
168+
const result = getResult(response)
169+
170+
t.falsy(result.errors, 'addBook mutation returned errors')
171+
t.truthy(result.data?.addBook, 'addBook should return the created book')
172+
t.truthy(result.data.addBook.id, 'Created book should have an id')
173+
t.is(result.data.addBook.title, testTitle, 'Created book should have the correct title')
174+
t.is(result.data.addBook.author, testAuthor, 'Created book should have the correct author')
175+
})
176+
177+
test('addBook mutation - created book can be retrieved', async t => {
178+
const mutation = gql`
179+
mutation AddBook($title: String, $author: String) {
180+
addBook(title: $title, author: $author) {
181+
id
182+
title
183+
author
184+
}
185+
}
186+
`
187+
const testTitle = `Retrievable Book ${Date.now()}`
188+
const testAuthor = 'Another Author'
189+
190+
// Create the book
191+
const createResponse = await server.executeOperation({
192+
query: mutation,
193+
variables: { title: testTitle, author: testAuthor }
194+
})
195+
const createResult = getResult(createResponse)
196+
const createdId = createResult.data.addBook.id
197+
198+
// Retrieve it
199+
const query = gql`
200+
query GetBook($id: ID!) {
201+
book(id: $id) {
202+
id
203+
title
204+
author
205+
}
206+
}
207+
`
208+
const getResponse = await server.executeOperation({
209+
query,
210+
variables: { id: createdId }
211+
})
212+
const getResult_ = getResult(getResponse)
213+
214+
t.falsy(getResult_.errors)
215+
t.is(getResult_.data.book.id, createdId)
216+
t.is(getResult_.data.book.title, testTitle)
217+
t.is(getResult_.data.book.author, testAuthor)
218+
})
219+
220+
// ============================================
221+
// Field selection tests
222+
// ============================================
223+
224+
test('books query respects field selection', async t => {
225+
const query = gql`
226+
query {
227+
books {
228+
title
229+
}
230+
}
231+
`
232+
const response = await server.executeOperation({ query })
233+
const result = getResult(response)
234+
235+
t.falsy(result.errors)
236+
const book = result.data.books[0]
237+
t.truthy(book.title, 'Should have title')
238+
t.is(book.id, undefined, 'Should not have id when not requested')
239+
t.is(book.author, undefined, 'Should not have author when not requested')
240+
})

0 commit comments

Comments
 (0)