From ef65c4e6d31b5df36bfff3254d614f61bf659bed Mon Sep 17 00:00:00 2001 From: nsfisis Date: Wed, 31 Dec 2025 01:18:37 +0900 Subject: feat(api): include note data in Card and Study route responses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update Card GET endpoint to return note and field values when available, and Study GET endpoint to include note data for card display rendering. This enables the frontend to render note-based cards with their templates. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/server/routes/study.test.ts | 107 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 100 insertions(+), 7 deletions(-) (limited to 'src/server/routes/study.test.ts') diff --git a/src/server/routes/study.test.ts b/src/server/routes/study.test.ts index d709750..77cb15c 100644 --- a/src/server/routes/study.test.ts +++ b/src/server/routes/study.test.ts @@ -6,8 +6,11 @@ import { errorHandler } from "../middleware/index.js"; import type { Card, CardRepository, + CardWithNoteData, Deck, DeckRepository, + Note, + NoteFieldValue, ReviewLog, ReviewLogRepository, } from "../repositories/index.js"; @@ -24,6 +27,7 @@ function createMockCardRepo(): CardRepository { softDelete: vi.fn(), softDeleteByNoteId: vi.fn(), findDueCards: vi.fn(), + findDueCardsWithNoteData: vi.fn(), updateFSRSFields: vi.fn(), }; } @@ -114,9 +118,47 @@ function createMockReviewLog(overrides: Partial = {}): ReviewLog { }; } +function createMockCardWithNoteData( + overrides: Partial = {}, +): CardWithNoteData { + return { + ...createMockCard(overrides), + note: overrides.note ?? null, + fieldValues: overrides.fieldValues ?? [], + }; +} + +function createMockNote(overrides: Partial = {}): Note { + return { + id: "note-uuid-123", + deckId: "deck-uuid-123", + noteTypeId: "note-type-uuid-123", + createdAt: new Date("2024-01-01"), + updatedAt: new Date("2024-01-01"), + deletedAt: null, + syncVersion: 0, + ...overrides, + }; +} + +function createMockNoteFieldValue( + overrides: Partial = {}, +): NoteFieldValue { + return { + id: "field-value-uuid-123", + noteId: "note-uuid-123", + noteFieldTypeId: "field-type-uuid-123", + value: "Test value", + createdAt: new Date("2024-01-01"), + updatedAt: new Date("2024-01-01"), + syncVersion: 0, + ...overrides, + }; +} + interface StudyResponse { card?: Card; - cards?: Card[]; + cards?: CardWithNoteData[]; error?: { code: string; message: string; @@ -153,7 +195,7 @@ describe("GET /api/decks/:deckId/study", () => { vi.mocked(mockDeckRepo.findById).mockResolvedValue( createMockDeck({ id: DECK_ID }), ); - vi.mocked(mockCardRepo.findDueCards).mockResolvedValue([]); + vi.mocked(mockCardRepo.findDueCardsWithNoteData).mockResolvedValue([]); const res = await app.request(`/api/decks/${DECK_ID}/study`, { method: "GET", @@ -167,22 +209,36 @@ describe("GET /api/decks/:deckId/study", () => { DECK_ID, "user-uuid-123", ); - expect(mockCardRepo.findDueCards).toHaveBeenCalledWith( + expect(mockCardRepo.findDueCardsWithNoteData).toHaveBeenCalledWith( DECK_ID, expect.any(Date), 100, ); }); - it("returns due cards", async () => { + it("returns due cards with note data", async () => { const mockCards = [ - createMockCard({ id: "card-1", front: "Q1", back: "A1" }), - createMockCard({ id: "card-2", front: "Q2", back: "A2" }), + createMockCardWithNoteData({ + id: "card-1", + front: "Q1", + back: "A1", + note: null, + fieldValues: [], + }), + createMockCardWithNoteData({ + id: "card-2", + front: "Q2", + back: "A2", + note: null, + fieldValues: [], + }), ]; vi.mocked(mockDeckRepo.findById).mockResolvedValue( createMockDeck({ id: DECK_ID }), ); - vi.mocked(mockCardRepo.findDueCards).mockResolvedValue(mockCards); + vi.mocked(mockCardRepo.findDueCardsWithNoteData).mockResolvedValue( + mockCards, + ); const res = await app.request(`/api/decks/${DECK_ID}/study`, { method: "GET", @@ -194,6 +250,43 @@ describe("GET /api/decks/:deckId/study", () => { expect(body.cards).toHaveLength(2); }); + it("returns due cards with note and field values when available", async () => { + const mockNote = createMockNote({ id: "note-1" }); + const mockFieldValues = [ + createMockNoteFieldValue({ noteId: "note-1", value: "Front" }), + createMockNoteFieldValue({ + id: "fv-2", + noteId: "note-1", + value: "Back", + }), + ]; + const mockCards = [ + createMockCardWithNoteData({ + id: "card-1", + noteId: "note-1", + note: mockNote, + fieldValues: mockFieldValues, + }), + ]; + vi.mocked(mockDeckRepo.findById).mockResolvedValue( + createMockDeck({ id: DECK_ID }), + ); + vi.mocked(mockCardRepo.findDueCardsWithNoteData).mockResolvedValue( + mockCards, + ); + + const res = await app.request(`/api/decks/${DECK_ID}/study`, { + method: "GET", + headers: { Authorization: `Bearer ${authToken}` }, + }); + + expect(res.status).toBe(200); + const body = (await res.json()) as StudyResponse; + expect(body.cards).toHaveLength(1); + expect(body.cards?.[0]?.note?.id).toBe("note-1"); + expect(body.cards?.[0]?.fieldValues).toHaveLength(2); + }); + it("returns 404 for non-existent deck", async () => { vi.mocked(mockDeckRepo.findById).mockResolvedValue(undefined); -- cgit v1.2.3-70-g09d2