Add unit tests for TavernCardValidator (#5366)

* Add unit tests for TavernCardValidator
This commit is contained in:
Tony Gies
2026-04-05 13:03:18 -05:00
committed by GitHub
parent 760263b78a
commit 8d8e3833f6
+251
View File
@@ -0,0 +1,251 @@
import { describe, test, expect } from '@jest/globals';
import { TavernCardValidator } from '../src/validator/TavernCardValidator';
const V1_FIELDS = ['name', 'description', 'personality', 'scenario', 'first_mes', 'mes_example'];
const V2_DATA_FIELDS = [
'name', 'description', 'personality', 'scenario', 'first_mes', 'mes_example',
'creator_notes', 'system_prompt', 'post_history_instructions',
'alternate_greetings', 'tags', 'creator', 'character_version', 'extensions',
];
function makeV1Card() {
return Object.fromEntries(V1_FIELDS.map(f => [f, '']));
}
function makeV2Card() {
return {
spec: 'chara_card_v2',
spec_version: '2.0',
data: {
...Object.fromEntries(V2_DATA_FIELDS.map(f => [f, ''])),
alternate_greetings: [],
tags: [],
extensions: {},
},
};
}
function makeV3Card() {
return {
spec: 'chara_card_v3',
spec_version: '3.0',
data: {},
};
}
describe('TavernCardValidator', () => {
describe('validate', () => {
test('returns 1 for valid V1 card', () => {
const v = new TavernCardValidator(makeV1Card());
expect(v.validate()).toBe(1);
});
test('returns 2 for valid V2 card', () => {
const v = new TavernCardValidator(makeV2Card());
expect(v.validate()).toBe(2);
});
test('returns 3 for valid V3 card', () => {
const v = new TavernCardValidator(makeV3Card());
expect(v.validate()).toBe(3);
});
test('returns false for empty object', () => {
const v = new TavernCardValidator({});
expect(v.validate()).toBe(false);
});
test('prefers V1 when card satisfies both V1 and V2', () => {
const card = { ...makeV1Card(), ...makeV2Card() };
const v = new TavernCardValidator(card);
expect(v.validate()).toBe(1);
});
});
describe('validateV1', () => {
test('accepts card with all required fields', () => {
const v = new TavernCardValidator(makeV1Card());
expect(v.validateV1()).toBe(true);
});
for (const field of V1_FIELDS) {
test(`rejects card missing ${field}`, () => {
const card = makeV1Card();
delete card[field];
const v = new TavernCardValidator(card);
expect(v.validateV1()).toBe(false);
expect(v.lastValidationError).toBe(field);
});
}
});
describe('validateV2', () => {
test('accepts valid V2 card', () => {
const v = new TavernCardValidator(makeV2Card());
expect(v.validateV2()).toBe(true);
});
test('rejects wrong spec string', () => {
const card = makeV2Card();
card.spec = 'chara_card_v1';
const v = new TavernCardValidator(card);
expect(v.validateV2()).toBe(false);
expect(v.lastValidationError).toBe('spec');
});
test('rejects wrong spec_version', () => {
const card = makeV2Card();
card.spec_version = '1.0';
const v = new TavernCardValidator(card);
expect(v.validateV2()).toBe(false);
expect(v.lastValidationError).toBe('spec_version');
});
test('rejects missing data', () => {
const card = makeV2Card();
delete card.data;
const v = new TavernCardValidator(card);
expect(v.validateV2()).toBe(false);
});
for (const field of V2_DATA_FIELDS) {
test(`rejects card missing data.${field}`, () => {
const card = makeV2Card();
delete card.data[field];
const v = new TavernCardValidator(card);
expect(v.validateV2()).toBe(false);
expect(v.lastValidationError).toBe(`data.${field}`);
});
}
test('rejects non-array alternate_greetings', () => {
const card = makeV2Card();
card.data.alternate_greetings = 'not an array';
const v = new TavernCardValidator(card);
expect(v.validateV2()).toBe(false);
});
test('rejects non-array tags', () => {
const card = makeV2Card();
card.data.tags = 'not an array';
const v = new TavernCardValidator(card);
expect(v.validateV2()).toBe(false);
});
test('rejects non-object extensions', () => {
const card = makeV2Card();
card.data.extensions = 'not an object';
const v = new TavernCardValidator(card);
expect(v.validateV2()).toBe(false);
});
test('accepts card with optional character_book', () => {
const card = makeV2Card();
card.data.character_book = {
extensions: {},
entries: [],
};
const v = new TavernCardValidator(card);
expect(v.validateV2()).toBe(true);
});
test('rejects character_book missing entries', () => {
const card = makeV2Card();
card.data.character_book = { extensions: {} };
const v = new TavernCardValidator(card);
expect(v.validateV2()).toBe(false);
expect(v.lastValidationError).toBe('data.character_book.entries');
});
test('rejects character_book missing extensions', () => {
const card = makeV2Card();
card.data.character_book = { entries: [] };
const v = new TavernCardValidator(card);
expect(v.validateV2()).toBe(false);
expect(v.lastValidationError).toBe('data.character_book.extensions');
});
test('rejects character_book with non-array entries', () => {
const card = makeV2Card();
card.data.character_book = { extensions: {}, entries: 'not array' };
const v = new TavernCardValidator(card);
expect(v.validateV2()).toBe(false);
});
test('rejects character_book with non-object extensions', () => {
const card = makeV2Card();
card.data.character_book = { extensions: 'not object', entries: [] };
const v = new TavernCardValidator(card);
expect(v.validateV2()).toBe(false);
});
});
describe('validateV3', () => {
test('accepts valid V3 card', () => {
const v = new TavernCardValidator(makeV3Card());
expect(v.validateV3()).toBe(true);
});
test('rejects wrong spec string', () => {
const card = makeV3Card();
card.spec = 'chara_card_v2';
const v = new TavernCardValidator(card);
expect(v.validateV3()).toBe(false);
expect(v.lastValidationError).toBe('spec');
});
test('rejects spec_version below 3.0', () => {
const card = makeV3Card();
card.spec_version = '2.9';
const v = new TavernCardValidator(card);
expect(v.validateV3()).toBe(false);
expect(v.lastValidationError).toBe('spec_version');
});
test('rejects spec_version at or above 4.0', () => {
const card = makeV3Card();
card.spec_version = '4.0';
const v = new TavernCardValidator(card);
expect(v.validateV3()).toBe(false);
});
test('accepts spec_version 3.5', () => {
const card = makeV3Card();
card.spec_version = '3.5';
const v = new TavernCardValidator(card);
expect(v.validateV3()).toBe(true);
});
test('rejects missing data', () => {
const card = makeV3Card();
delete card.data;
const v = new TavernCardValidator(card);
expect(v.validateV3()).toBe(false);
});
test('rejects non-object data', () => {
const card = makeV3Card();
card.data = 'not an object';
const v = new TavernCardValidator(card);
expect(v.validateV3()).toBe(false);
});
});
describe('lastValidationError', () => {
test('is null after successful validation', () => {
const v = new TavernCardValidator(makeV1Card());
v.validate();
expect(v.lastValidationError).toBeNull();
});
test('is reset on each validate call', () => {
const v = new TavernCardValidator({});
v.validate();
expect(v.lastValidationError).not.toBeNull();
v.card = makeV1Card();
v.validate();
expect(v.lastValidationError).toBeNull();
});
});
});