Files
diegosouzapw--omniroute/tests/unit/memory-schemas-roundtrip.test.ts
2026-07-13 13:39:12 +08:00

427 lines
16 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import {
MemorySettingsExtendedSchema,
MemoryUpdatePutSchema,
RetrievePreviewSchema,
MemoryReindexSchema,
MemorySummarizeSchema,
EmbeddingProviderListingSchema,
MemoryEngineStatusSchema,
RetrievePreviewResultSchema,
} from "../../src/shared/schemas/memory.ts";
import {
QdrantSettingsSchema,
QdrantSettingsUpdateSchema,
QdrantSearchSchema,
QdrantHealthResultSchema,
} from "../../src/shared/schemas/qdrant.ts";
test("memory schema module keeps the runtime request/response schemas exported", () => {
assert.equal(typeof MemorySettingsExtendedSchema.safeParse, "function");
assert.equal(typeof MemoryUpdatePutSchema.safeParse, "function");
assert.equal(typeof RetrievePreviewSchema.safeParse, "function");
assert.equal(typeof MemoryReindexSchema.safeParse, "function");
assert.equal(typeof MemorySummarizeSchema.safeParse, "function");
assert.equal(typeof EmbeddingProviderListingSchema.safeParse, "function");
assert.equal(typeof MemoryEngineStatusSchema.safeParse, "function");
assert.equal(typeof RetrievePreviewResultSchema.safeParse, "function");
});
// ---------------------------------------------------------------------------
// 1. MemorySettingsExtendedSchema
// ---------------------------------------------------------------------------
test("MemorySettingsExtendedSchema: accepts fully-populated valid payload", () => {
const result = MemorySettingsExtendedSchema.safeParse({
enabled: true,
maxTokens: 4000,
retentionDays: 30,
strategy: "hybrid",
skillsEnabled: false,
embeddingSource: "auto",
embeddingProviderModel: "openai/text-embedding-3-small",
transformersEnabled: false,
staticEnabled: true,
rerankEnabled: false,
rerankProviderModel: null,
vectorStore: "sqlite-vec",
});
assert.equal(result.success, true, "Should accept all valid fields");
});
test("MemorySettingsExtendedSchema: rejects extra field (strict)", () => {
const result = MemorySettingsExtendedSchema.safeParse({
enabled: true,
unknownExtraField: "nope",
});
assert.equal(result.success, false, "Strict schema must reject unknown keys");
});
test("MemorySettingsExtendedSchema: rejects maxTokens above max (16000)", () => {
const result = MemorySettingsExtendedSchema.safeParse({ maxTokens: 16001 });
assert.equal(result.success, false, "maxTokens 16001 must be rejected");
});
test("MemorySettingsExtendedSchema: rejects invalid embeddingSource value", () => {
const result = MemorySettingsExtendedSchema.safeParse({ embeddingSource: "magic" });
assert.equal(result.success, false, "Unknown embeddingSource must be rejected");
});
// ---------------------------------------------------------------------------
// 2. MemoryUpdatePutSchema
// ---------------------------------------------------------------------------
test("MemoryUpdatePutSchema: accepts valid partial update (content only)", () => {
const result = MemoryUpdatePutSchema.safeParse({ content: "updated content" });
assert.equal(result.success, true, "Should accept partial update with only content");
if (result.success) {
assert.equal(result.data.content, "updated content");
}
});
test("MemoryUpdatePutSchema: rejects extra field (strict)", () => {
const result = MemoryUpdatePutSchema.safeParse({ content: "x", extra: true });
assert.equal(result.success, false, "Strict schema must reject unknown keys");
});
test("MemoryUpdatePutSchema: rejects empty-string key", () => {
const result = MemoryUpdatePutSchema.safeParse({ key: "" });
assert.equal(result.success, false, "key must be min(1)");
});
test("MemoryUpdatePutSchema: rejects invalid type enum", () => {
const result = MemoryUpdatePutSchema.safeParse({ type: "unknown_type" });
assert.equal(result.success, false, "Invalid memory type must be rejected");
});
// ---------------------------------------------------------------------------
// 3. RetrievePreviewSchema
// ---------------------------------------------------------------------------
test("RetrievePreviewSchema: accepts minimal valid payload (query only)", () => {
const result = RetrievePreviewSchema.safeParse({ query: "what is the capital of France?" });
assert.equal(result.success, true, "Should accept minimal payload with query only");
if (result.success) {
assert.equal(result.data.strategy, "hybrid", "Default strategy should be hybrid");
assert.equal(result.data.maxTokens, 2000, "Default maxTokens should be 2000");
assert.equal(result.data.limit, 20, "Default limit should be 20");
}
});
test("RetrievePreviewSchema: rejects empty query string", () => {
const result = RetrievePreviewSchema.safeParse({ query: "" });
assert.equal(result.success, false, "Empty query must be rejected");
});
test("RetrievePreviewSchema: rejects limit above 100", () => {
const result = RetrievePreviewSchema.safeParse({ query: "test", limit: 101 });
assert.equal(result.success, false, "limit > 100 must be rejected");
});
// ---------------------------------------------------------------------------
// 4. MemoryReindexSchema
// ---------------------------------------------------------------------------
test("MemoryReindexSchema: accepts empty object (all defaults)", () => {
const result = MemoryReindexSchema.safeParse({});
assert.equal(result.success, true, "Should accept empty object with defaults applied");
if (result.success) {
assert.equal(result.data.force, false, "Default force should be false");
}
});
test("MemoryReindexSchema: rejects extra field (strict)", () => {
const result = MemoryReindexSchema.safeParse({ force: true, extra: "not allowed" });
assert.equal(result.success, false, "Strict schema must reject unknown keys");
});
// ---------------------------------------------------------------------------
// 5. MemorySummarizeSchema
// ---------------------------------------------------------------------------
test("MemorySummarizeSchema: accepts valid payload with all fields", () => {
const result = MemorySummarizeSchema.safeParse({
olderThanDays: 60,
apiKeyId: "key-abc",
dryRun: true,
});
assert.equal(result.success, true, "Should accept all valid fields");
});
test("MemorySummarizeSchema: rejects olderThanDays above 365", () => {
const result = MemorySummarizeSchema.safeParse({ olderThanDays: 366 });
assert.equal(result.success, false, "olderThanDays > 365 must be rejected");
});
test("MemorySummarizeSchema: rejects olderThanDays of 0 (positive required)", () => {
const result = MemorySummarizeSchema.safeParse({ olderThanDays: 0 });
assert.equal(result.success, false, "olderThanDays 0 must be rejected (must be positive)");
});
// ---------------------------------------------------------------------------
// 6. EmbeddingProviderListingSchema
// ---------------------------------------------------------------------------
test("EmbeddingProviderListingSchema: accepts valid providers array", () => {
const result = EmbeddingProviderListingSchema.safeParse({
providers: [
{
provider: "openai",
hasKey: true,
models: [
{ id: "openai/text-embedding-3-small", name: "text-embedding-3-small", dimensions: 1536 },
{ id: "openai/text-embedding-ada-002", name: "text-embedding-ada-002", dimensions: null },
],
},
],
});
assert.equal(result.success, true, "Should accept valid provider listing");
});
test("EmbeddingProviderListingSchema: rejects missing required model fields", () => {
const result = EmbeddingProviderListingSchema.safeParse({
providers: [
{
provider: "openai",
hasKey: true,
models: [{ id: "openai/text-embedding-3-small" }], // missing name and dimensions
},
],
});
assert.equal(result.success, false, "Missing model name/dimensions must be rejected");
});
// ---------------------------------------------------------------------------
// 7. MemoryEngineStatusSchema
// ---------------------------------------------------------------------------
test("MemoryEngineStatusSchema: accepts valid fully-populated status", () => {
const result = MemoryEngineStatusSchema.safeParse({
keyword: { available: true, backend: "FTS5" },
embedding: {
source: "remote",
model: "openai/text-embedding-3-small",
dimensions: 1536,
available: true,
reason: "provider openai com key configurada",
cacheStats: { hits: 10, misses: 2, size: 12 },
},
vectorStore: {
backend: "sqlite-vec",
available: true,
rowCount: 42,
needsReindex: 0,
reason: "sqlite-vec loaded",
},
qdrant: { enabled: false, healthy: null, latencyMs: null, error: null },
rerank: {
enabled: false,
provider: null,
model: null,
available: false,
reason: "no rerank provider configured",
},
});
assert.equal(result.success, true, "Should accept fully-populated engine status");
});
test("MemoryEngineStatusSchema: rejects wrong literal for keyword.backend", () => {
const result = MemoryEngineStatusSchema.safeParse({
keyword: { available: true, backend: "BM25" }, // wrong backend literal
embedding: {
source: null,
model: null,
dimensions: null,
available: false,
reason: "none",
cacheStats: { hits: 0, misses: 0, size: 0 },
},
vectorStore: { backend: "none", available: false, rowCount: 0, needsReindex: 0, reason: "" },
qdrant: { enabled: false, healthy: null, latencyMs: null, error: null },
rerank: { enabled: false, provider: null, model: null, available: false, reason: "" },
});
assert.equal(result.success, false, "backend 'BM25' must be rejected (must be literal 'FTS5')");
});
test("MemoryEngineStatusSchema: rejects invalid vectorStore backend", () => {
const result = MemoryEngineStatusSchema.safeParse({
keyword: { available: true, backend: "FTS5" },
embedding: {
source: null,
model: null,
dimensions: null,
available: false,
reason: "",
cacheStats: { hits: 0, misses: 0, size: 0 },
},
vectorStore: { backend: "faiss", available: false, rowCount: 0, needsReindex: 0, reason: "" },
qdrant: { enabled: false, healthy: null, latencyMs: null, error: null },
rerank: { enabled: false, provider: null, model: null, available: false, reason: "" },
});
assert.equal(result.success, false, "backend 'faiss' must be rejected");
});
// ---------------------------------------------------------------------------
// 8. RetrievePreviewResultSchema
// ---------------------------------------------------------------------------
test("RetrievePreviewResultSchema: accepts valid response with memories", () => {
const result = RetrievePreviewResultSchema.safeParse({
memories: [
{
id: "mem-1",
type: "factual",
key: "capital_of_france",
content: "The capital of France is Paris.",
score: 0.95,
tokens: 10,
tier: "hybrid-rrf",
vecScore: 0.93,
ftsScore: 0.88,
},
],
resolution: {
embeddingSource: "remote",
embeddingModel: "openai/text-embedding-3-small",
vectorStore: "sqlite-vec",
strategyUsed: "hybrid",
rerankApplied: false,
fallbackReason: null,
},
totalTokensUsed: 10,
budgetMaxTokens: 2000,
});
assert.equal(result.success, true, "Should accept valid preview result");
});
test("RetrievePreviewResultSchema: rejects invalid tier value", () => {
const result = RetrievePreviewResultSchema.safeParse({
memories: [
{
id: "mem-1",
type: "factual",
key: "k",
content: "c",
score: 0.5,
tokens: 5,
tier: "bm25", // invalid tier
vecScore: null,
ftsScore: null,
},
],
resolution: {
embeddingSource: null,
embeddingModel: null,
vectorStore: "none",
strategyUsed: "exact",
rerankApplied: false,
fallbackReason: null,
},
totalTokensUsed: 5,
budgetMaxTokens: 2000,
});
assert.equal(result.success, false, "tier 'bm25' must be rejected");
});
// ---------------------------------------------------------------------------
// 9. QdrantSettingsSchema
// ---------------------------------------------------------------------------
test("QdrantSettingsSchema: accepts valid settings with defaults applied", () => {
const result = QdrantSettingsSchema.safeParse({
enabled: true,
host: "localhost",
});
assert.equal(result.success, true, "Should accept minimal settings with defaults");
if (result.success) {
assert.equal(result.data.port, 6333, "Default port should be 6333");
assert.equal(result.data.collection, "omniroute_memory", "Default collection");
assert.equal(result.data.quantization, "none", "Default quantization should be none");
assert.equal(result.data.hasApiKey, false, "Default hasApiKey should be false");
assert.equal(result.data.apiKeyMasked, null, "Default apiKeyMasked should be null");
}
});
test("QdrantSettingsSchema: rejects port above 65535", () => {
const result = QdrantSettingsSchema.safeParse({
enabled: false,
host: "",
port: 99999,
});
assert.equal(result.success, false, "Port 99999 must be rejected");
});
// ---------------------------------------------------------------------------
// 10. QdrantSettingsUpdateSchema
// ---------------------------------------------------------------------------
test("QdrantSettingsUpdateSchema: accepts valid partial update", () => {
const result = QdrantSettingsUpdateSchema.safeParse({
enabled: true,
host: "qdrant.example.com",
port: 6334,
});
assert.equal(result.success, true, "Should accept partial update");
});
test("QdrantSettingsUpdateSchema: rejects extra field (strict)", () => {
const result = QdrantSettingsUpdateSchema.safeParse({
enabled: true,
unknownField: "not allowed",
});
assert.equal(result.success, false, "Strict schema must reject unknown keys");
});
test("QdrantSettingsUpdateSchema: rejects empty collection string", () => {
const result = QdrantSettingsUpdateSchema.safeParse({ collection: "" });
assert.equal(result.success, false, "collection min(1) must reject empty string");
});
// ---------------------------------------------------------------------------
// 11. QdrantSearchSchema
// ---------------------------------------------------------------------------
test("QdrantSearchSchema: accepts valid search payload with default topK", () => {
const result = QdrantSearchSchema.safeParse({ query: "semantic search test" });
assert.equal(result.success, true, "Should accept query with default topK");
if (result.success) {
assert.equal(result.data.topK, 5, "Default topK should be 5");
}
});
test("QdrantSearchSchema: rejects topK above 50", () => {
const result = QdrantSearchSchema.safeParse({ query: "test", topK: 51 });
assert.equal(result.success, false, "topK > 50 must be rejected");
});
test("QdrantSearchSchema: rejects empty query string", () => {
const result = QdrantSearchSchema.safeParse({ query: "" });
assert.equal(result.success, false, "Empty query must be rejected");
});
// ---------------------------------------------------------------------------
// 12. QdrantHealthResultSchema (bonus — extra coverage)
// ---------------------------------------------------------------------------
test("QdrantHealthResultSchema: accepts healthy result without error field", () => {
const result = QdrantHealthResultSchema.safeParse({ ok: true, latencyMs: 12 });
assert.equal(result.success, true, "Healthy result must be accepted");
});
test("QdrantHealthResultSchema: accepts unhealthy result with error field", () => {
const result = QdrantHealthResultSchema.safeParse({
ok: false,
latencyMs: 0,
error: "connection refused",
});
assert.equal(result.success, true, "Unhealthy result with error string must be accepted");
});
test("QdrantHealthResultSchema: rejects non-boolean ok field", () => {
const result = QdrantHealthResultSchema.safeParse({ ok: "yes", latencyMs: 10 });
assert.equal(result.success, false, "ok must be boolean");
});