Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 4 additions & 20 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,33 +39,17 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

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

# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2

# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl

# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language

#- run: |
# make bootstrap
# make release
uses: github/codeql-action/autobuild@v3

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
15 changes: 6 additions & 9 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,23 @@
name: Run tests

on: [push, pull_request]
# push:
# branches: [ master ]
# pull_request:
# branches: [ master ]

jobs:
test:
runs-on: ubuntu-latest

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

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
# - run: npm run build --if-present
- run: npx prisma generate
- run: npm test
240 changes: 240 additions & 0 deletions src/schema/books/booksSchema.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import test from 'ava'
import gql from 'graphql-tag'

import { server } from '../../server.js'
import { bookResolvers } from './index.js'
import { client } from '../../providers/booksDB/dbClient.js'

// Helper to extract result from Apollo Server 5 response
const getResult = (response) => {
if (response.body.kind !== 'single') {
throw new Error('Expected single result')
}
return response.body.singleResult
}

// Helper to create a test book via mutation
const createTestBook = async (title = `Test Book ${Date.now()}`, author = 'Test Author') => {
const mutation = gql`
mutation AddBook($title: String, $author: String) {
addBook(title: $title, author: $author) {
id
title
author
}
}
`
const response = await server.executeOperation({
query: mutation,
variables: { title, author }
})
return getResult(response).data.addBook
}

// Clean up after all tests
test.after.always(async () => {
await client.$disconnect()
})

// ============================================
// Resolver structure tests
// ============================================

test('Book resolvers include all expected functions', t => {
t.truthy(bookResolvers?.Query?.books, 'books resolver missing')
t.true(typeof bookResolvers.Query.books === 'function', 'books resolver is not a function')

t.truthy(bookResolvers?.Query?.book, 'book resolver missing')
t.true(typeof bookResolvers.Query.book === 'function', 'book resolver is not a function')

t.truthy(bookResolvers?.Mutation?.addBook, 'addBook resolver missing')
t.true(typeof bookResolvers.Mutation.addBook === 'function', 'addBook resolver is not a function')
})

// ============================================
// Query tests
// ============================================

test('books query returns an array', async t => {
const query = gql`
query {
books {
id
title
author
}
}
`
const response = await server.executeOperation({ query })
const result = getResult(response)

t.falsy(result.errors, 'books query returned errors')
t.true(Array.isArray(result.data?.books), 'books should return an array')
})

test('books query returns books with correct structure', async t => {
// Ensure at least one book exists
await createTestBook('Structure Test Book', 'Structure Author')

const query = gql`
query {
books {
id
title
author
}
}
`
const response = await server.executeOperation({ query })
const result = getResult(response)

t.falsy(result.errors)
t.true(result.data.books.length >= 1, 'Should have at least one book')

const book = result.data.books[0]
t.truthy(book.id, 'Book should have an id')
t.truthy(book.title, 'Book should have a title')
t.truthy(book.author, 'Book should have an author')
})

test('book query returns a single book by id', async t => {
// Create a book to query
const createdBook = await createTestBook('Single Query Test', 'Query Author')
const bookId = createdBook.id

const query = gql`
query GetBook($id: ID!) {
book(id: $id) {
id
title
author
}
}
`
const response = await server.executeOperation({
query,
variables: { id: bookId }
})
const result = getResult(response)

t.falsy(result.errors, 'book query returned errors')
t.truthy(result.data?.book, 'book query should return a book')
t.is(result.data.book.id, bookId, 'Returned book should have the requested id')
t.is(result.data.book.title, 'Single Query Test')
t.is(result.data.book.author, 'Query Author')
})

test('book query returns null for non-existent id', async t => {
const query = gql`
query GetBook($id: ID!) {
book(id: $id) {
id
title
author
}
}
`
const response = await server.executeOperation({
query,
variables: { id: '99999' }
})
const result = getResult(response)

t.falsy(result.errors, 'Should not return errors for non-existent book')
t.is(result.data?.book, null, 'Should return null for non-existent book')
})

// ============================================
// Mutation tests
// ============================================

test('addBook mutation creates a new book', async t => {
const mutation = gql`
mutation AddBook($title: String, $author: String) {
addBook(title: $title, author: $author) {
id
title
author
}
}
`
const testTitle = `Test Book ${Date.now()}`
const testAuthor = 'Test Author'

const response = await server.executeOperation({
query: mutation,
variables: { title: testTitle, author: testAuthor }
})
const result = getResult(response)

t.falsy(result.errors, 'addBook mutation returned errors')
t.truthy(result.data?.addBook, 'addBook should return the created book')
t.truthy(result.data.addBook.id, 'Created book should have an id')
t.is(result.data.addBook.title, testTitle, 'Created book should have the correct title')
t.is(result.data.addBook.author, testAuthor, 'Created book should have the correct author')
})

test('addBook mutation - created book can be retrieved', async t => {
const mutation = gql`
mutation AddBook($title: String, $author: String) {
addBook(title: $title, author: $author) {
id
title
author
}
}
`
const testTitle = `Retrievable Book ${Date.now()}`
const testAuthor = 'Another Author'

// Create the book
const createResponse = await server.executeOperation({
query: mutation,
variables: { title: testTitle, author: testAuthor }
})
const createResult = getResult(createResponse)
const createdId = createResult.data.addBook.id

// Retrieve it
const query = gql`
query GetBook($id: ID!) {
book(id: $id) {
id
title
author
}
}
`
const getResponse = await server.executeOperation({
query,
variables: { id: createdId }
})
const getResult_ = getResult(getResponse)

t.falsy(getResult_.errors)
t.is(getResult_.data.book.id, createdId)
t.is(getResult_.data.book.title, testTitle)
t.is(getResult_.data.book.author, testAuthor)
})

// ============================================
// Field selection tests
// ============================================

test('books query respects field selection', async t => {
const query = gql`
query {
books {
title
}
}
`
const response = await server.executeOperation({ query })
const result = getResult(response)

t.falsy(result.errors)
const book = result.data.books[0]
t.truthy(book.title, 'Should have title')
t.is(book.id, undefined, 'Should not have id when not requested')
t.is(book.author, undefined, 'Should not have author when not requested')
})
Loading