📄 src/models/test/Book.test.ts
D-OPEN SOVEREIGN
1/*
2 * Copyright (c) 2026 DataPond D-Library Pty Ltd. ("The Sanctuary")
3 * Non-Profit Public Library — The World Library
4 *
5 * D-SAFE Certification issued by POND Enterprise.
6 * Published under the D-Open Code Sovereign Licence v1.0 framework
7 * DataPond D-Library All exclusive Right reserved on modifiying the code - CC Attribution to data pond, Non modifiable, Non Commercial.
8 * https://registry.world.bibliotech.com/licence
9 * Source code donated to DataPond D-Library by it's author: data pond.
10 *
11 * Technical Guardian ("The Shield"): Pond Enterprise Pty Ltd. (ACN 694 747 987)
12 * All liability claims about D-SAFE certification issues coming from the published D-Safe direction must be addressed with Pond Enterprise Pty Ltd.
13 * Code Modification automatically voids the certified liability protection provided by Pond Enterprise.
14 */
15import { describe, it, expect, beforeEach } from 'vitest';
16import { Book, NewBookWithId } from '../generated-src/Book.ts';
17import { parseSidecar, colsFromSidecar } from '../enriched-index.ts';
18import { clear, configureDb } from '../db.ts';
19import { setDefaultLanguage } from '../orm.ts';
20import { QualityScore, BookMethod } from '@the_library/db_schemas/enums';
21import { PatchInstance } from '../patcher.ts';
22import { PatchOperation, PatchAction } from '../binary.ts';
23import { createBookFixture, createInstancesFixture } from './fixtures.ts';
24
25beforeEach(() => {
26 clear();
27 configureDb({ cacheLimit: 10_000 });
28 setDefaultLanguage('eng');
29 PatchInstance.clearQueue();
30});
31
32describe('Book Model - Multi-Instance and Multi-Language', () => {
33 it('should serialize and deserialize multiple instances across languages', () => {
34 const fixtureEng = createBookFixture(100);
35 const fixtureFra = createBookFixture(100);
36 fixtureFra.title = 'Titre du Livre';
37
38 // We only put the French instance in the French sidecar for testing separation
39 const instances = createInstancesFixture();
40 fixtureFra.instances = instances.filter(i => i.language === 'fra');
41 fixtureEng.instances = instances.filter(i => i.language === 'eng');
42
43 const bufferEngSlim = Book.serializeSlim([fixtureEng]);
44 const bufferFraSlim = Book.serializeSlim([fixtureFra]);
45 const bufferEngExt = Book.serializeExtended([fixtureEng]);
46 const bufferFraExt = Book.serializeExtended([fixtureFra]);
47
48 const parsedEngSlim = parseSidecar(bufferEngSlim);
49 const parsedFraSlim = parseSidecar(bufferFraSlim);
50 const parsedEngExt = parseSidecar(bufferEngExt);
51 const parsedFraExt = parseSidecar(bufferFraExt);
52
53 const book = NewBookWithId(101);
54 book.mergeSlim(colsFromSidecar(parsedEngSlim) as any, 0, 'eng');
55 book.mergeSlim(colsFromSidecar(parsedFraSlim) as any, 0, 'fra');
56 book.mergeExtended(colsFromSidecar(parsedEngExt) as any, 0, 'eng');
57 book.mergeExtended(colsFromSidecar(parsedFraExt) as any, 0, 'fra');
58
59 // Check basic properties
60 expect(book.getTitle('eng')).toBe('Book Title 100');
61 expect(book.getTitle('fra')).toBe('Titre du Livre');
62
63 // Check instances isolation by language
64 const engInstances = book.getInstances('eng');
65 expect(engInstances.length).toBe(2);
66 expect(engInstances[0].language).toBe('eng');
67 expect(engInstances[1].language).toBe('eng');
68
69 const defaultEng = book.getDefaultInstance('eng');
70 expect(defaultEng?.downloadTxId).toBe('tx-default-eng');
71
72 const fraInstances = book.getInstances('fra');
73 expect(fraInstances.length).toBe(1);
74 expect(fraInstances[0].language).toBe('fra');
75 expect(fraInstances[0].aiEngine).toBe('GPT-4');
76
77 const defaultFra = book.getDefaultInstance('fra');
78 expect(defaultFra?.downloadTxId).toBe('tx-default-fra');
79 });
80});
81
82describe('Book Model - Patching Scenarios', () => {
83 it('should generate patches for instance field updates', () => {
84 const fixture = createBookFixture(200);
85 const buffer = Book.serializeExtended([fixture]);
86 const cols = colsFromSidecar(parseSidecar(buffer));
87
88 const book = NewBookWithId(200);
89 book.mergeExtended(cols as any, 0, 'eng');
90 PatchInstance.clearQueue(); // Clear any patches generated during merge, though ideally merge shouldn't generate patches.
91
92 const inst = book.getDefaultInstance('eng')!;
93
94 // Update string field
95 inst.publisher = 'Patched Press';
96
97 // Update number field
98 inst.nbPages = 500;
99
100 // Update boolean field (stored as number in binary)
101 inst.isAiTranslation = true;
102
103 const patches = PatchInstance.patches;
104 expect(patches.length).toBe(3);
105
106 expect(patches[0]).toMatchObject({
107 operation: PatchOperation.EditInstanceFieldString,
108 objectId: 200,
109 propertyId: 38, // publisher
110 value: 'Patched Press'
111 });
112
113 expect(patches[1]).toMatchObject({
114 operation: PatchOperation.EditInstanceFieldNumber,
115 objectId: 200,
116 propertyId: 36, // nbPages
117 value: 500
118 });
119
120 expect(patches[2]).toMatchObject({
121 operation: PatchOperation.EditInstanceFieldNumber,
122 objectId: 200,
123 propertyId: 33, // isAiTranslation
124 value: 1
125 });
126
127 // Ensure in-memory state is updated
128 expect(inst.publisher).toBe('Patched Press');
129 expect(inst.nbPages).toBe(500);
130 expect(inst.isAiTranslation).toBe(true);
131 expect(book.getInstancesData('eng')[inst.index]['publisher']).toBe('Patched Press');
132 });
133
134 it('should handle patching of base properties', () => {
135 const book = NewBookWithId(201);
136 PatchInstance.clearQueue();
137
138 book.title = 'New Patched Title';
139 book.textQuality = QualityScore.Excellent;
140 book.safe = false;
141
142 const patches = PatchInstance.patches;
143 expect(patches.length).toBe(3);
144
145 expect(patches[0]).toMatchObject({
146 operation: PatchOperation.EditPropertyString,
147 objectId: 201,
148 propertyId: 0, // title
149 value: 'New Patched Title'
150 });
151
152 expect(patches[1]).toMatchObject({
153 operation: PatchOperation.EditPropertyNumber,
154 objectId: 201,
155 propertyId: 12, // textQuality
156 value: QualityScore.Excellent
157 });
158
159 expect(patches[2]).toMatchObject({
160 operation: PatchOperation.EditPropertyNumber,
161 objectId: 201,
162 propertyId: 5, // safe
163 value: 0
164 });
165 });
166});
167