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
122 changes: 65 additions & 57 deletions __tests__/app/api/books/generate/route.test.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,78 @@
import { parseBookMetadata } from "@/lib/book-utils"
import { BookMetadata } from "@/lib/types"
import { POST } from "@/app/api/books/generate/route"
import { NextResponse } from "next/server"

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.

Unused import NextResponse.

Suggested change
import { NextResponse } from "next/server"

Copilot uses AI. Check for mistakes.

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",
}
// Mock NextResponse
jest.mock("next/server", () => ({
NextResponse: {
json: jest.fn().mockImplementation((body, options) => ({
status: options?.status || 200,
json: async () => body,
...body,
})),
},
}))

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",
// Mock next/headers
jest.mock("next/headers", () => ({
cookies: jest.fn().mockReturnValue(
Promise.resolve({
getAll: jest.fn().mockReturnValue([]),
get: jest.fn().mockReturnValue(undefined),
})
})
),
}))

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
jest.mock("@supabase/auth-helpers-nextjs", () => ({
createRouteHandlerClient: jest.fn().mockReturnValue({
auth: {
getSession: jest.fn().mockResolvedValue({ data: { session: null } }),
},
}),
}))

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",
})
})
jest.mock("@supabase/supabase-js", () => ({
createClient: jest.fn().mockReturnValue({
from: jest.fn().mockReturnValue({
insert: jest.fn().mockReturnValue({
select: jest.fn().mockReturnValue({
single: jest.fn().mockResolvedValue({ data: { id: "test-id" }, error: null }),
}),
}),
}),
}),
}))

it("should handle missing optional fields gracefully", () => {
const minimalBook: BookMetadata = {
title: "Minimal Book",
} as any
jest.mock("@/lib/id_generator", () => ({
generateBookId: jest.fn().mockResolvedValue("TEST-CODE"),
}))

const result = parseBookMetadata(minimalBook)
jest.mock("@/lib/book-utils", () => ({
parseBookMetadata: jest.fn().mockReturnValue({
title: "Test Title",
author: "Test Author",
cover_url: "http://test.com/cover.jpg",
isbn: "1234567890",
}),
}))

expect(result).toEqual({
title: "Minimal Book",
author: "Unknown Author",
cover_url: null,
isbn: null,
describe("POST /api/books/generate", () => {
it("should generate a book code successfully", async () => {
const request = new Request("http://localhost:3000/api/books/generate", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
})
})

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,
jest.spyOn(request, "json").mockResolvedValue({
location: { lat: 40.7128, long: -74.006 },
book: { title: "Test Book" },
anonymousId: "anon-123",
})

const response = await POST(request)
const data = await response.json()

expect(data.code).toBe("TEST-CODE")
})
})
2 changes: 1 addition & 1 deletion __tests__/navigation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ describe("Navigation", () => {
})

expect(screen.getByText("TaleTrail")).toBeInTheDocument()
expect(screen.getByText("Search")).toBeInTheDocument()

expect(screen.getByText("Generate Code")).toBeInTheDocument()
expect(screen.queryByText("Login")).not.toBeInTheDocument()
})
Expand Down
4 changes: 2 additions & 2 deletions app/api/books/generate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export async function POST(request: Request) {

// 1. Check user session (optional but good context)
// Use defaults (implicitly uses NEXT_PUBLIC_SUPABASE_ANON_KEY)
const cookieStore = cookies()
const supabase = createRouteHandlerClient({ cookies: () => cookieStore })
const cookieStore = await cookies()
const supabase = createRouteHandlerClient({ cookies: () => cookieStore } as any)
Comment on lines +24 to +25

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 as any type assertion bypasses TypeScript's type safety. This could hide type mismatches between what createRouteHandlerClient expects and what's being provided. Consider properly typing the cookies parameter or reviewing if there's a better approach to handle the async cookies function.

Suggested change
const cookieStore = await cookies()
const supabase = createRouteHandlerClient({ cookies: () => cookieStore } as any)
const supabase = createRouteHandlerClient({ cookies })

Copilot uses AI. Check for mistakes.
const {
data: { session },
} = await supabase.auth.getSession()
Expand Down
13 changes: 5 additions & 8 deletions app/api/sightings/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,11 @@ export async function POST(request: Request) {
const { bookId, location } = await request.json()

// Auth Check: Use Anon Key with Session
const cookieStore = cookies()
const supabase = createRouteHandlerClient(
{ cookies: () => cookieStore },
{
supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL,
supabaseKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
}
)
const cookieStore = await cookies()
const supabase = createRouteHandlerClient({ cookies: () => cookieStore } as any, {
supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL,
supabaseKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
})
Comment on lines +41 to +45

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 as any type assertion bypasses TypeScript's type safety. This could hide type mismatches between what createRouteHandlerClient expects and what's being provided. Consider properly typing the cookies parameter or reviewing if there's a better approach to handle the async cookies function.

Suggested change
const cookieStore = await cookies()
const supabase = createRouteHandlerClient({ cookies: () => cookieStore } as any, {
supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL,
supabaseKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
})
const cookieStore = cookies()
const supabase = createRouteHandlerClient(
{ cookies: () => cookieStore },
{
supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL,
supabaseKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
}
)

Copilot uses AI. Check for mistakes.

const {
data: { user },
Expand Down
Loading