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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ node_modules/
.backups/
*.code-workspace
.eslintrc.json
next-env.d.ts
70 changes: 70 additions & 0 deletions __tests__/app/api/books/generate/route.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { parseBookMetadata } from "@/lib/book-utils"
import { BookMetadata } from "@/lib/types"

describe("parseBookMetadata", () => {
it("should parse Google Books data correctly", () => {
const googleBook: BookMetadata = {
title: "Google Book Title",
authors: ["Author One", "Author Two"],
coverUrl: "http://example.com/cover.jpg",
isbn: "1234567890",
googleId: "g1",
}

const result = parseBookMetadata(googleBook)

expect(result).toEqual({
title: "Google Book Title",
author: "Author One, Author Two",
cover_url: "http://example.com/cover.jpg",
isbn: "1234567890",
})
})

it("should parse OpenLibrary data correctly", () => {
const olBook: BookMetadata = {
title: "OL Book Title",
author_name: ["OL Author"],
cover_edition_key: "OL123M",
isbn: ["0987654321"],
key: "/works/OL123W",
// Force type to satisfy simplified mock if needed, but the structure matches
edition_key: ["OL123M"],
publish_year: [2021],
} as any // Cast as any because OpenLibraryDoc has many required fields
Comment on lines +25 to +34

Copilot AI Dec 20, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OpenLibraryDoc type definition has all fields marked as required, but the test mock casts to any because some fields (like key, edition_key, publish_year) are not part of the actual type definition. This indicates a mismatch between the actual OpenLibrary API response structure and the type definition. Consider making optional fields in OpenLibraryDoc optional (e.g., cover_edition_key?, isbn?) to better reflect the actual data structure and avoid the need for unsafe type casts.

Copilot uses AI. Check for mistakes.

const result = parseBookMetadata(olBook)

expect(result).toEqual({
title: "OL Book Title",
author: "OL Author",
cover_url: "https://covers.openlibrary.org/b/olid/OL123M-M.jpg", // Expecting getBookCover logic
isbn: "0987654321",
})
})

it("should handle missing optional fields gracefully", () => {
const minimalBook: BookMetadata = {
title: "Minimal Book",
} as any

const result = parseBookMetadata(minimalBook)

expect(result).toEqual({
title: "Minimal Book",
author: "Unknown Author",
cover_url: null,
isbn: null,
})
})

it("should return defaults for null input", () => {
const result = parseBookMetadata(null as any)
expect(result).toEqual({
title: "Unknown Title",
author: "Unknown Author",
cover_url: null,
isbn: null,
})
})
})
Comment on lines +1 to +70

Copilot AI Dec 20, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test file only tests the parseBookMetadata utility function, but doesn't test the actual POST API route handler. The route handler contains important business logic including authentication, book code generation, database insertions, and error handling that should be covered by tests. Consider adding integration tests for the POST handler similar to the pattern used in __tests__/app/api/sightings/claim/route.test.ts.

Copilot uses AI. Check for mistakes.
23 changes: 17 additions & 6 deletions app/api/books/generate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { generateBookId } from "@/lib/id_generator"
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs"
import { cookies } from "next/headers"
import { createClient } from "@supabase/supabase-js"
import { parseBookMetadata } from "@/lib/book-utils"
import { OpenLibraryDoc, getBookCover } from "@/lib/openLibrary"

Copilot AI Dec 20, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The imports OpenLibraryDoc and getBookCover are no longer used directly in this file after the refactoring. The parseBookMetadata helper now handles the OpenLibrary logic internally. These unused imports should be removed to keep the code clean.

Suggested change
import { OpenLibraryDoc, getBookCover } from "@/lib/openLibrary"

Copilot uses AI. Check for mistakes.
import { BookMetadata } from "@/lib/types"

export const dynamic = "force-dynamic"

Expand Down Expand Up @@ -40,9 +42,16 @@ export async function POST(request: Request) {
})

// 4. Prepare Metadata
const typedBook = book as OpenLibraryDoc
const cover_url = typedBook ? getBookCover(typedBook) : null
const isbn = typedBook?.isbn ? typedBook.isbn[0] : null
// The 'book' object might be an OpenLibraryDoc or a GoogleBookData object
const bookData = book as BookMetadata

// Parse metadata using helper to handle different formats safely
const { title, author, cover_url: cover_link, isbn } = parseBookMetadata(bookData)

if (!title || title === "Unknown Title") {
console.error("Attempted to generate book with no title", bookData)
return NextResponse.json({ error: "Book title is required" }, { status: 400 })
}

// 5. Persist Book
const { data, error } = await adminSupabase
Expand All @@ -51,10 +60,10 @@ export async function POST(request: Request) {
code,
lat,
lon: long,
title: typedBook?.title || "Unknown Title",
author: typedBook?.author_name?.[0] || "Unknown Author",
title: title,
author: author,
isbn: isbn,
cover_url: cover_url,
cover_url: cover_link,
location: `${lat},${long}`,
})
.select()
Expand Down Expand Up @@ -91,3 +100,5 @@ export async function POST(request: Request) {
return NextResponse.json({ error: "Failed to create code" }, { status: 500 })
}
}

// Helper moved to @/lib/book-utils
Loading