Blame Raw
Cohee · 51ad27fb · · 251 lines (8.6 KB)
2 contributors
1import { describe, test, expect } from '@jest/globals';
2import { TavernCardValidator } from '../src/validator/TavernCardValidator';
3
4const V1_FIELDS = ['name', 'description', 'personality', 'scenario', 'first_mes', 'mes_example'];
5
6const V2_DATA_FIELDS = [
7 'name', 'description', 'personality', 'scenario', 'first_mes', 'mes_example',
8 'creator_notes', 'system_prompt', 'post_history_instructions',
9 'alternate_greetings', 'tags', 'creator', 'character_version', 'extensions',
10];
11
12function makeV1Card() {
13 return Object.fromEntries(V1_FIELDS.map(f => [f, '']));
14}
15
16function makeV2Card() {
17 return {
18 spec: 'chara_card_v2',
19 spec_version: '2.0',
20 data: {
21 ...Object.fromEntries(V2_DATA_FIELDS.map(f => [f, ''])),
22 alternate_greetings: [],
23 tags: [],
24 extensions: {},
25 },
26 };
27}
28
29function makeV3Card() {
30 return {
31 spec: 'chara_card_v3',
32 spec_version: '3.0',
33 data: {},
34 };
35}
36
37describe('TavernCardValidator', () => {
38 describe('validate', () => {
39 test('returns 1 for valid V1 card', () => {
40 const v = new TavernCardValidator(makeV1Card());
41 expect(v.validate()).toBe(1);
42 });
43
44 test('returns 2 for valid V2 card', () => {
45 const v = new TavernCardValidator(makeV2Card());
46 expect(v.validate()).toBe(2);
47 });
48
49 test('returns 3 for valid V3 card', () => {
50 const v = new TavernCardValidator(makeV3Card());
51 expect(v.validate()).toBe(3);
52 });
53
54 test('returns false for empty object', () => {
55 const v = new TavernCardValidator({});
56 expect(v.validate()).toBe(false);
57 });
58
59 test('prefers V1 when card satisfies both V1 and V2', () => {
60 const card = { ...makeV1Card(), ...makeV2Card() };
61 const v = new TavernCardValidator(card);
62 expect(v.validate()).toBe(1);
63 });
64 });
65
66 describe('validateV1', () => {
67 test('accepts card with all required fields', () => {
68 const v = new TavernCardValidator(makeV1Card());
69 expect(v.validateV1()).toBe(true);
70 });
71
72 for (const field of V1_FIELDS) {
73 test(`rejects card missing ${field}`, () => {
74 const card = makeV1Card();
75 delete card[field];
76 const v = new TavernCardValidator(card);
77 expect(v.validateV1()).toBe(false);
78 expect(v.lastValidationError).toBe(field);
79 });
80 }
81 });
82
83 describe('validateV2', () => {
84 test('accepts valid V2 card', () => {
85 const v = new TavernCardValidator(makeV2Card());
86 expect(v.validateV2()).toBe(true);
87 });
88
89 test('rejects wrong spec string', () => {
90 const card = makeV2Card();
91 card.spec = 'chara_card_v1';
92 const v = new TavernCardValidator(card);
93 expect(v.validateV2()).toBe(false);
94 expect(v.lastValidationError).toBe('spec');
95 });
96
97 test('rejects wrong spec_version', () => {
98 const card = makeV2Card();
99 card.spec_version = '1.0';
100 const v = new TavernCardValidator(card);
101 expect(v.validateV2()).toBe(false);
102 expect(v.lastValidationError).toBe('spec_version');
103 });
104
105 test('rejects missing data', () => {
106 const card = makeV2Card();
107 delete card.data;
108 const v = new TavernCardValidator(card);
109 expect(v.validateV2()).toBe(false);
110 });
111
112 for (const field of V2_DATA_FIELDS) {
113 test(`rejects card missing data.${field}`, () => {
114 const card = makeV2Card();
115 delete card.data[field];
116 const v = new TavernCardValidator(card);
117 expect(v.validateV2()).toBe(false);
118 expect(v.lastValidationError).toBe(`data.${field}`);
119 });
120 }
121
122 test('rejects non-array alternate_greetings', () => {
123 const card = makeV2Card();
124 card.data.alternate_greetings = 'not an array';
125 const v = new TavernCardValidator(card);
126 expect(v.validateV2()).toBe(false);
127 });
128
129 test('rejects non-array tags', () => {
130 const card = makeV2Card();
131 card.data.tags = 'not an array';
132 const v = new TavernCardValidator(card);
133 expect(v.validateV2()).toBe(false);
134 });
135
136 test('rejects non-object extensions', () => {
137 const card = makeV2Card();
138 card.data.extensions = 'not an object';
139 const v = new TavernCardValidator(card);
140 expect(v.validateV2()).toBe(false);
141 });
142
143 test('accepts card with optional character_book', () => {
144 const card = makeV2Card();
145 card.data.character_book = {
146 extensions: {},
147 entries: [],
148 };
149 const v = new TavernCardValidator(card);
150 expect(v.validateV2()).toBe(true);
151 });
152
153 test('rejects character_book missing entries', () => {
154 const card = makeV2Card();
155 card.data.character_book = { extensions: {} };
156 const v = new TavernCardValidator(card);
157 expect(v.validateV2()).toBe(false);
158 expect(v.lastValidationError).toBe('data.character_book.entries');
159 });
160
161 test('rejects character_book missing extensions', () => {
162 const card = makeV2Card();
163 card.data.character_book = { entries: [] };
164 const v = new TavernCardValidator(card);
165 expect(v.validateV2()).toBe(false);
166 expect(v.lastValidationError).toBe('data.character_book.extensions');
167 });
168
169 test('rejects character_book with non-array entries', () => {
170 const card = makeV2Card();
171 card.data.character_book = { extensions: {}, entries: 'not array' };
172 const v = new TavernCardValidator(card);
173 expect(v.validateV2()).toBe(false);
174 });
175
176 test('rejects character_book with non-object extensions', () => {
177 const card = makeV2Card();
178 card.data.character_book = { extensions: 'not object', entries: [] };
179 const v = new TavernCardValidator(card);
180 expect(v.validateV2()).toBe(false);
181 });
182 });
183
184 describe('validateV3', () => {
185 test('accepts valid V3 card', () => {
186 const v = new TavernCardValidator(makeV3Card());
187 expect(v.validateV3()).toBe(true);
188 });
189
190 test('rejects wrong spec string', () => {
191 const card = makeV3Card();
192 card.spec = 'chara_card_v2';
193 const v = new TavernCardValidator(card);
194 expect(v.validateV3()).toBe(false);
195 expect(v.lastValidationError).toBe('spec');
196 });
197
198 test('rejects spec_version below 3.0', () => {
199 const card = makeV3Card();
200 card.spec_version = '2.9';
201 const v = new TavernCardValidator(card);
202 expect(v.validateV3()).toBe(false);
203 expect(v.lastValidationError).toBe('spec_version');
204 });
205
206 test('rejects spec_version at or above 4.0', () => {
207 const card = makeV3Card();
208 card.spec_version = '4.0';
209 const v = new TavernCardValidator(card);
210 expect(v.validateV3()).toBe(false);
211 });
212
213 test('accepts spec_version 3.5', () => {
214 const card = makeV3Card();
215 card.spec_version = '3.5';
216 const v = new TavernCardValidator(card);
217 expect(v.validateV3()).toBe(true);
218 });
219
220 test('rejects missing data', () => {
221 const card = makeV3Card();
222 delete card.data;
223 const v = new TavernCardValidator(card);
224 expect(v.validateV3()).toBe(false);
225 });
226
227 test('rejects non-object data', () => {
228 const card = makeV3Card();
229 card.data = 'not an object';
230 const v = new TavernCardValidator(card);
231 expect(v.validateV3()).toBe(false);
232 });
233 });
234
235 describe('lastValidationError', () => {
236 test('is null after successful validation', () => {
237 const v = new TavernCardValidator(makeV1Card());
238 v.validate();
239 expect(v.lastValidationError).toBeNull();
240 });
241
242 test('is reset on each validate call', () => {
243 const v = new TavernCardValidator({});
244 v.validate();
245 expect(v.lastValidationError).not.toBeNull();
246 v.card = makeV1Card();
247 v.validate();
248 expect(v.lastValidationError).toBeNull();
249 });
250 });
251});