aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/server/routes/sync.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/routes/sync.test.ts')
-rw-r--r--src/server/routes/sync.test.ts370
1 files changed, 361 insertions, 9 deletions
diff --git a/src/server/routes/sync.test.ts b/src/server/routes/sync.test.ts
index 7492b49..1107acd 100644
--- a/src/server/routes/sync.test.ts
+++ b/src/server/routes/sync.test.ts
@@ -7,7 +7,15 @@ import type {
SyncPushResult,
SyncRepository,
} from "../repositories/sync.js";
-import type { Card, Deck, ReviewLog } from "../repositories/types.js";
+import type {
+ Card,
+ Deck,
+ Note,
+ NoteFieldType,
+ NoteFieldValue,
+ NoteType,
+ ReviewLog,
+} from "../repositories/types.js";
import { createSyncRouter } from "./sync.js";
function createMockSyncRepo(): SyncRepository {
@@ -35,9 +43,17 @@ interface SyncPushResponse {
decks?: { id: string; syncVersion: number }[];
cards?: { id: string; syncVersion: number }[];
reviewLogs?: { id: string; syncVersion: number }[];
+ noteTypes?: { id: string; syncVersion: number }[];
+ noteFieldTypes?: { id: string; syncVersion: number }[];
+ notes?: { id: string; syncVersion: number }[];
+ noteFieldValues?: { id: string; syncVersion: number }[];
conflicts?: {
decks: string[];
cards: string[];
+ noteTypes: string[];
+ noteFieldTypes: string[];
+ notes: string[];
+ noteFieldValues: string[];
};
error?: {
code: string;
@@ -76,7 +92,18 @@ describe("POST /api/sync/push", () => {
decks: [],
cards: [],
reviewLogs: [],
- conflicts: { decks: [], cards: [] },
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ conflicts: {
+ decks: [],
+ cards: [],
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ },
};
vi.mocked(mockSyncRepo.pushChanges).mockResolvedValue(mockResult);
@@ -94,11 +121,18 @@ describe("POST /api/sync/push", () => {
expect(body.decks).toEqual([]);
expect(body.cards).toEqual([]);
expect(body.reviewLogs).toEqual([]);
- expect(body.conflicts).toEqual({ decks: [], cards: [] });
+ expect(body.noteTypes).toEqual([]);
+ expect(body.noteFieldTypes).toEqual([]);
+ expect(body.notes).toEqual([]);
+ expect(body.noteFieldValues).toEqual([]);
expect(mockSyncRepo.pushChanges).toHaveBeenCalledWith(userId, {
decks: [],
cards: [],
reviewLogs: [],
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
});
});
@@ -117,7 +151,18 @@ describe("POST /api/sync/push", () => {
decks: [{ id: "deck-uuid-123", syncVersion: 1 }],
cards: [],
reviewLogs: [],
- conflicts: { decks: [], cards: [] },
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ conflicts: {
+ decks: [],
+ cards: [],
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ },
};
vi.mocked(mockSyncRepo.pushChanges).mockResolvedValue(mockResult);
@@ -141,6 +186,8 @@ describe("POST /api/sync/push", () => {
const cardData = {
id: "550e8400-e29b-41d4-a716-446655440001",
deckId: "550e8400-e29b-41d4-a716-446655440000",
+ noteId: null,
+ isReversed: null,
front: "Question",
back: "Answer",
state: 0,
@@ -161,7 +208,18 @@ describe("POST /api/sync/push", () => {
decks: [],
cards: [{ id: "550e8400-e29b-41d4-a716-446655440001", syncVersion: 1 }],
reviewLogs: [],
- conflicts: { decks: [], cards: [] },
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ conflicts: {
+ decks: [],
+ cards: [],
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ },
};
vi.mocked(mockSyncRepo.pushChanges).mockResolvedValue(mockResult);
@@ -198,7 +256,18 @@ describe("POST /api/sync/push", () => {
reviewLogs: [
{ id: "550e8400-e29b-41d4-a716-446655440002", syncVersion: 1 },
],
- conflicts: { decks: [], cards: [] },
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ conflicts: {
+ decks: [],
+ cards: [],
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ },
};
vi.mocked(mockSyncRepo.pushChanges).mockResolvedValue(mockResult);
@@ -238,7 +307,18 @@ describe("POST /api/sync/push", () => {
decks: [{ id: "550e8400-e29b-41d4-a716-446655440003", syncVersion: 5 }],
cards: [],
reviewLogs: [],
- conflicts: { decks: ["550e8400-e29b-41d4-a716-446655440003"], cards: [] },
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ conflicts: {
+ decks: ["550e8400-e29b-41d4-a716-446655440003"],
+ cards: [],
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ },
};
vi.mocked(mockSyncRepo.pushChanges).mockResolvedValue(mockResult);
@@ -355,6 +435,8 @@ describe("POST /api/sync/push", () => {
const cardData = {
id: "550e8400-e29b-41d4-a716-446655440005",
deckId: "550e8400-e29b-41d4-a716-446655440004",
+ noteId: null,
+ isReversed: null,
front: "Q",
back: "A",
state: 0,
@@ -388,7 +470,18 @@ describe("POST /api/sync/push", () => {
reviewLogs: [
{ id: "550e8400-e29b-41d4-a716-446655440006", syncVersion: 1 },
],
- conflicts: { decks: [], cards: [] },
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ conflicts: {
+ decks: [],
+ cards: [],
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ },
};
vi.mocked(mockSyncRepo.pushChanges).mockResolvedValue(mockResult);
@@ -412,6 +505,121 @@ describe("POST /api/sync/push", () => {
expect(body.reviewLogs).toHaveLength(1);
});
+ it("successfully pushes note types", async () => {
+ const noteTypeData = {
+ id: "550e8400-e29b-41d4-a716-446655440010",
+ name: "Basic Note",
+ frontTemplate: "{{Front}}",
+ backTemplate: "{{Back}}",
+ isReversible: false,
+ createdAt: "2024-01-01T00:00:00.000Z",
+ updatedAt: "2024-01-02T00:00:00.000Z",
+ deletedAt: null,
+ };
+
+ const mockResult: SyncPushResult = {
+ decks: [],
+ cards: [],
+ reviewLogs: [],
+ noteTypes: [
+ { id: "550e8400-e29b-41d4-a716-446655440010", syncVersion: 1 },
+ ],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ conflicts: {
+ decks: [],
+ cards: [],
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ },
+ };
+ vi.mocked(mockSyncRepo.pushChanges).mockResolvedValue(mockResult);
+
+ const res = await app.request("/api/sync/push", {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ decks: [],
+ cards: [],
+ reviewLogs: [],
+ noteTypes: [noteTypeData],
+ }),
+ });
+
+ expect(res.status).toBe(200);
+ const body = (await res.json()) as SyncPushResponse;
+ expect(body.noteTypes).toHaveLength(1);
+ expect(body.noteTypes?.[0]?.id).toBe(
+ "550e8400-e29b-41d4-a716-446655440010",
+ );
+ });
+
+ it("successfully pushes cards with noteId and isReversed", async () => {
+ const cardData = {
+ id: "550e8400-e29b-41d4-a716-446655440011",
+ deckId: "550e8400-e29b-41d4-a716-446655440000",
+ noteId: "550e8400-e29b-41d4-a716-446655440020",
+ isReversed: true,
+ front: "Question",
+ back: "Answer",
+ state: 0,
+ due: "2024-01-01T00:00:00.000Z",
+ stability: 0,
+ difficulty: 0,
+ elapsedDays: 0,
+ scheduledDays: 0,
+ reps: 0,
+ lapses: 0,
+ lastReview: null,
+ createdAt: "2024-01-01T00:00:00.000Z",
+ updatedAt: "2024-01-02T00:00:00.000Z",
+ deletedAt: null,
+ };
+
+ const mockResult: SyncPushResult = {
+ decks: [],
+ cards: [{ id: "550e8400-e29b-41d4-a716-446655440011", syncVersion: 1 }],
+ reviewLogs: [],
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ conflicts: {
+ decks: [],
+ cards: [],
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ },
+ };
+ vi.mocked(mockSyncRepo.pushChanges).mockResolvedValue(mockResult);
+
+ const res = await app.request("/api/sync/push", {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ decks: [],
+ cards: [cardData],
+ reviewLogs: [],
+ }),
+ });
+
+ expect(res.status).toBe(200);
+ const body = (await res.json()) as SyncPushResponse;
+ expect(body.cards).toHaveLength(1);
+ expect(body.cards?.[0]?.id).toBe("550e8400-e29b-41d4-a716-446655440011");
+ });
+
it("handles soft-deleted entities", async () => {
const deletedDeck = {
id: "550e8400-e29b-41d4-a716-446655440007",
@@ -427,7 +635,18 @@ describe("POST /api/sync/push", () => {
decks: [{ id: "550e8400-e29b-41d4-a716-446655440007", syncVersion: 2 }],
cards: [],
reviewLogs: [],
- conflicts: { decks: [], cards: [] },
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ conflicts: {
+ decks: [],
+ cards: [],
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ },
};
vi.mocked(mockSyncRepo.pushChanges).mockResolvedValue(mockResult);
@@ -454,6 +673,10 @@ interface SyncPullResponse {
decks?: Deck[];
cards?: Card[];
reviewLogs?: ReviewLog[];
+ noteTypes?: NoteType[];
+ noteFieldTypes?: NoteFieldType[];
+ notes?: Note[];
+ noteFieldValues?: NoteFieldValue[];
currentSyncVersion?: number;
error?: {
code: string;
@@ -500,6 +723,10 @@ describe("GET /api/sync/pull", () => {
decks: [mockDeck],
cards: [],
reviewLogs: [],
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
currentSyncVersion: 1,
};
vi.mocked(mockSyncRepo.pullChanges).mockResolvedValue(mockResult);
@@ -515,6 +742,10 @@ describe("GET /api/sync/pull", () => {
expect(body.decks).toHaveLength(1);
expect(body.cards).toHaveLength(0);
expect(body.reviewLogs).toHaveLength(0);
+ expect(body.noteTypes).toHaveLength(0);
+ expect(body.noteFieldTypes).toHaveLength(0);
+ expect(body.notes).toHaveLength(0);
+ expect(body.noteFieldValues).toHaveLength(0);
expect(body.currentSyncVersion).toBe(1);
expect(mockSyncRepo.pullChanges).toHaveBeenCalledWith(userId, {
lastSyncVersion: 0,
@@ -526,6 +757,10 @@ describe("GET /api/sync/pull", () => {
decks: [],
cards: [],
reviewLogs: [],
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
currentSyncVersion: 5,
};
vi.mocked(mockSyncRepo.pullChanges).mockResolvedValue(mockResult);
@@ -562,6 +797,10 @@ describe("GET /api/sync/pull", () => {
decks: [mockDeck],
cards: [],
reviewLogs: [],
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
currentSyncVersion: 2,
};
vi.mocked(mockSyncRepo.pullChanges).mockResolvedValue(mockResult);
@@ -607,6 +846,10 @@ describe("GET /api/sync/pull", () => {
decks: [],
cards: [mockCard],
reviewLogs: [],
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
currentSyncVersion: 3,
};
vi.mocked(mockSyncRepo.pullChanges).mockResolvedValue(mockResult);
@@ -645,6 +888,10 @@ describe("GET /api/sync/pull", () => {
decks: [],
cards: [],
reviewLogs: [mockReviewLog],
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
currentSyncVersion: 1,
};
vi.mocked(mockSyncRepo.pullChanges).mockResolvedValue(mockResult);
@@ -717,6 +964,10 @@ describe("GET /api/sync/pull", () => {
decks: [mockDeck],
cards: [mockCard],
reviewLogs: [mockReviewLog],
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
currentSyncVersion: 3,
};
vi.mocked(mockSyncRepo.pullChanges).mockResolvedValue(mockResult);
@@ -732,9 +983,106 @@ describe("GET /api/sync/pull", () => {
expect(body.decks).toHaveLength(1);
expect(body.cards).toHaveLength(1);
expect(body.reviewLogs).toHaveLength(1);
+ expect(body.noteTypes).toHaveLength(0);
+ expect(body.noteFieldTypes).toHaveLength(0);
+ expect(body.notes).toHaveLength(0);
+ expect(body.noteFieldValues).toHaveLength(0);
expect(body.currentSyncVersion).toBe(3);
});
+ it("returns note types", async () => {
+ const mockNoteType: NoteType = {
+ id: "550e8400-e29b-41d4-a716-446655440010",
+ userId,
+ name: "Basic Note",
+ frontTemplate: "{{Front}}",
+ backTemplate: "{{Back}}",
+ isReversible: false,
+ createdAt: new Date("2024-01-01T00:00:00.000Z"),
+ updatedAt: new Date("2024-01-02T00:00:00.000Z"),
+ deletedAt: null,
+ syncVersion: 1,
+ };
+
+ const mockResult: SyncPullResult = {
+ decks: [],
+ cards: [],
+ reviewLogs: [],
+ noteTypes: [mockNoteType],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ currentSyncVersion: 1,
+ };
+ vi.mocked(mockSyncRepo.pullChanges).mockResolvedValue(mockResult);
+
+ const res = await app.request("/api/sync/pull", {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ },
+ });
+
+ expect(res.status).toBe(200);
+ const body = (await res.json()) as SyncPullResponse;
+ expect(body.noteTypes).toHaveLength(1);
+ expect(body.noteTypes?.[0]?.id).toBe(
+ "550e8400-e29b-41d4-a716-446655440010",
+ );
+ expect(body.noteTypes?.[0]?.name).toBe("Basic Note");
+ expect(body.noteTypes?.[0]?.frontTemplate).toBe("{{Front}}");
+ expect(body.noteTypes?.[0]?.isReversible).toBe(false);
+ });
+
+ it("returns cards with noteId and isReversed fields", async () => {
+ const mockCard: Card = {
+ id: "550e8400-e29b-41d4-a716-446655440001",
+ deckId: "550e8400-e29b-41d4-a716-446655440000",
+ noteId: "550e8400-e29b-41d4-a716-446655440020",
+ isReversed: true,
+ front: "Question",
+ back: "Answer",
+ state: 0,
+ due: new Date("2024-01-01T00:00:00.000Z"),
+ stability: 0,
+ difficulty: 0,
+ elapsedDays: 0,
+ scheduledDays: 0,
+ reps: 0,
+ lapses: 0,
+ lastReview: null,
+ createdAt: new Date("2024-01-01T00:00:00.000Z"),
+ updatedAt: new Date("2024-01-02T00:00:00.000Z"),
+ deletedAt: null,
+ syncVersion: 1,
+ };
+
+ const mockResult: SyncPullResult = {
+ decks: [],
+ cards: [mockCard],
+ reviewLogs: [],
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
+ currentSyncVersion: 1,
+ };
+ vi.mocked(mockSyncRepo.pullChanges).mockResolvedValue(mockResult);
+
+ const res = await app.request("/api/sync/pull", {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ },
+ });
+
+ expect(res.status).toBe(200);
+ const body = (await res.json()) as SyncPullResponse;
+ expect(body.cards).toHaveLength(1);
+ expect(body.cards?.[0]?.noteId).toBe(
+ "550e8400-e29b-41d4-a716-446655440020",
+ );
+ expect(body.cards?.[0]?.isReversed).toBe(true);
+ });
+
it("returns soft-deleted entities", async () => {
const deletedDeck: Deck = {
id: "550e8400-e29b-41d4-a716-446655440000",
@@ -752,6 +1100,10 @@ describe("GET /api/sync/pull", () => {
decks: [deletedDeck],
cards: [],
reviewLogs: [],
+ noteTypes: [],
+ noteFieldTypes: [],
+ notes: [],
+ noteFieldValues: [],
currentSyncVersion: 2,
};
vi.mocked(mockSyncRepo.pullChanges).mockResolvedValue(mockResult);