| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990 |
- import assert from 'node:assert/strict';
- import test from 'node:test';
- import type { ParseObject, ParseQueryResult } from '../src/db/parse-rest.client.js';
- import { VOC_PARSE_CLASSES } from '../src/db/parse-rest.schema.js';
- import { ParseRestProductKnowledgeStore, type ProductKnowledgeRecord } from '../src/modules/product-knowledge/product-knowledge.store.js';
- type StoredKnowledge = Omit<ProductKnowledgeRecord, 'id'> & { naturalKey: string } & ParseObject;
- class FakeKnowledgeClient {
- readonly records: StoredKnowledge[] = [];
- private nextId = 1;
- async find<T>(_className: string, options: { where?: Record<string, unknown>; limit?: number }): Promise<ParseQueryResult<T>> {
- return {
- results: this.records.filter((record) => matches(record, options.where ?? {}))
- .sort((left, right) => left.objectId.localeCompare(right.objectId))
- .slice(0, options.limit) as unknown as Array<T & ParseObject>,
- };
- }
- async findOne<T>(_className: string, where: Record<string, unknown>): Promise<(T & ParseObject) | null> {
- return (this.records.find((record) => matches(record, where)) ?? null) as (T & ParseObject) | null;
- }
- async create<T extends Record<string, unknown>>(className: string, object: T): Promise<ParseObject> {
- assert.equal(className, VOC_PARSE_CLASSES.productKnowledge);
- const now = '2026-07-27T00:00:00.000Z';
- const stored = { ...object, objectId: `knowledge-${this.nextId++}`, createdAt: now, updatedAt: now } as unknown as StoredKnowledge;
- this.records.push(stored);
- return stored;
- }
- async update<T extends Record<string, unknown>>(_className: string, objectId: string, patch: T): Promise<{ updatedAt: string }> {
- const record = this.records.find((item) => item.objectId === objectId);
- assert.ok(record);
- const updatedAt = '2026-07-27T01:00:00.000Z';
- Object.assign(record, patch, { updatedAt });
- return { updatedAt };
- }
- }
- function matches(record: StoredKnowledge, where: Record<string, unknown>): boolean {
- return Object.entries(where).every(([key, value]) => {
- if (key === 'objectId' && value && typeof value === 'object' && '$gt' in value) {
- return record.objectId > String((value as { $gt: unknown }).$gt);
- }
- return record[key as keyof StoredKnowledge] === value;
- });
- }
- test('product knowledge stays workspace scoped and updates without duplicates', async () => {
- const client = new FakeKnowledgeClient();
- const store = new ParseRestProductKnowledgeStore(client);
- const common = {
- productKey: 'jd:1001', productId: '1001', productRole: 'own' as const,
- featured: true, status: 'active' as const, tags: ['经营TOP', '经营TOP', ' 蒸烤箱 '],
- note: '重点跟踪', ownerUserId: 'operator', actorUserId: 'admin',
- };
- const created = await store.upsert({ workspaceId: 'demashi', ...common });
- await store.upsert({ workspaceId: 'other', ...common });
- const updated = await store.upsert({ workspaceId: 'demashi', ...common, note: '已复核', featured: false });
- assert.equal(client.records.length, 2);
- assert.equal(created.createdBy, 'admin');
- assert.deepEqual(created.tags, ['经营TOP', '蒸烤箱']);
- assert.equal(updated.note, '已复核');
- assert.equal(updated.featured, false);
- assert.equal((await store.list({ workspaceId: 'demashi', limit: 10, cursor: null })).items.length, 1);
- assert.equal((await store.list({ workspaceId: 'other', limit: 10, cursor: null })).items.length, 1);
- });
- test('product knowledge supports cursor paging and soft archive', async () => {
- const client = new FakeKnowledgeClient();
- const store = new ParseRestProductKnowledgeStore(client);
- for (const productId of ['1001', '1002', '1003']) {
- await store.upsert({
- workspaceId: 'demashi', productKey: `jd:${productId}`, productId, productRole: 'own',
- featured: true, status: 'active', tags: [], note: '', ownerUserId: '', actorUserId: 'admin',
- });
- }
- const first = await store.list({ workspaceId: 'demashi', limit: 2, cursor: null, featured: true });
- const second = await store.list({ workspaceId: 'demashi', limit: 2, cursor: first.nextCursor, featured: true });
- const archived = await store.archive('demashi', 'jd:1002', 'admin');
- assert.equal(first.items.length, 2);
- assert.ok(first.nextCursor);
- assert.deepEqual(second.items.map((item) => item.productId), ['1003']);
- assert.equal(archived?.status, 'archived');
- assert.equal(archived?.featured, false);
- });
|