import { DyFM_EnvironmentFlag } from '@futdevpro/fsm-dynamo'; import { DyNTS_global_settings } from '../../../_collections/global-settings.const'; import { DyNTS_GlobalService } from '../../../_services/core/global.service'; import { DyNTS_LVS_VectorPersist } from '../_models/data-models/lvs-vector-persist.data-model'; import { DyNTS_LVS_VectorPersist_DataService } from './lvs-vector-persist.data-service'; /** * BFR-AM-001 — a `DyNTS_LVS_VectorPersist_DataService` spec-jei. A base-ctor eager `getDBService`-jét * stub-oljuk (memory: dynts_dataservice_eager_resolve), így NEM kell élő Mongo. Az upsert atomikus * `$set`-jét (Mixed `metadata` — memory: mongoose_mixed_atomic_write) + a dimenzió-tárolást bizonyítja. */ describe('| DyNTS_LVS_VectorPersist_DataService', () => { let mockDBService: jasmine.SpyObj<{ find: () => Promise; findOne: () => Promise }>; beforeAll(() => { if (!DyNTS_global_settings.systemShortCodeName) { (DyNTS_global_settings as { systemShortCodeName?: string }).systemShortCodeName = 'TEST'; } if (!DyNTS_global_settings.env_settings) { (DyNTS_global_settings as { env_settings?: unknown }).env_settings = { environment: DyFM_EnvironmentFlag.local, }; } }); beforeEach(() => { mockDBService = jasmine.createSpyObj('DyNTS_DBService', ['find', 'findOne']); mockDBService.find.and.returnValue(Promise.resolve([])); mockDBService.findOne.and.returnValue(Promise.resolve(null)); spyOn(DyNTS_GlobalService, 'getDBService').and.returnValue(mockDBService as never); }); describe('| constructor', () => { it('| should create the service with a data model', () => { const data: DyNTS_LVS_VectorPersist = new DyNTS_LVS_VectorPersist({ collectionKey: 'pool-a', vectorId: 'v1', embedding: [1, 2, 3], }); const service: DyNTS_LVS_VectorPersist_DataService = new DyNTS_LVS_VectorPersist_DataService({ data: data, issuer: 'issuer-123' }); expect(service).toBeInstanceOf(DyNTS_LVS_VectorPersist_DataService); expect(service.data).toBeDefined(); expect(service.data.vectorId).toBe('v1'); expect(service.data.collectionKey).toBe('pool-a'); }); it('| should create the service without data', () => { const service: DyNTS_LVS_VectorPersist_DataService = new DyNTS_LVS_VectorPersist_DataService({ issuer: 'issuer-123' }); expect(service).toBeInstanceOf(DyNTS_LVS_VectorPersist_DataService); expect(service.data).toBeInstanceOf(DyNTS_LVS_VectorPersist); }); }); describe('| findByCollectionKey', () => { it('| should query the persisted vectors for a collectionKey', async () => { const service: DyNTS_LVS_VectorPersist_DataService = new DyNTS_LVS_VectorPersist_DataService({ issuer: 'issuer-123' }); const records: DyNTS_LVS_VectorPersist[] = [ new DyNTS_LVS_VectorPersist({ collectionKey: 'pool-a', vectorId: 'v1', embedding: [1, 0] }), ]; const findSpy = spyOn(service, 'findDataList').and.returnValue(Promise.resolve(records)); const result: DyNTS_LVS_VectorPersist[] = await service.findByCollectionKey('pool-a'); expect(findSpy).toHaveBeenCalledTimes(1); const filterArg = findSpy.calls.mostRecent().args[0] as { collectionKey: string }; expect(filterArg.collectionKey).toBe('pool-a'); expect(result).toBe(records); }); }); describe('| upsertVector — atomic $set + dimension store', () => { it('| should issue an updateOne $set (with dimensions) for an EXISTING record', async () => { const service: DyNTS_LVS_VectorPersist_DataService = new DyNTS_LVS_VectorPersist_DataService({ issuer: 'issuer-123' }); const existing: DyNTS_LVS_VectorPersist = new DyNTS_LVS_VectorPersist({ collectionKey: 'pool-a', vectorId: 'v1', embedding: [1, 2, 3], }); existing._id = 'rec-1'; spyOn(service, 'findData').and.returnValue(Promise.resolve(existing)); const updateSpy = spyOn(service, 'updateData').and.returnValue(Promise.resolve()); const saveSpy = spyOn(service, 'saveData').and.returnValue(Promise.resolve(existing)); await service.upsertVector({ collectionKey: 'pool-a', vectorId: 'v1', embedding: [4, 5, 6, 7], metadata: { tag: 'x' }, }); expect(saveSpy).not.toHaveBeenCalled(); expect(updateSpy).toHaveBeenCalledTimes(1); const arg = updateSpy.calls.mostRecent().args[0] as { filterBy: { _id: string }; update: { $set: { embedding: number[]; dimensions: number; metadata: unknown } }; }; expect(arg.filterBy._id).toBe('rec-1'); expect(arg.update.$set.embedding).toEqual([4, 5, 6, 7]); expect(arg.update.$set.dimensions).toBe(4); expect(arg.update.$set.metadata).toEqual({ tag: 'x' }); }); it('| should saveData a NEW record (with stored dimensions) when none exists', async () => { const service: DyNTS_LVS_VectorPersist_DataService = new DyNTS_LVS_VectorPersist_DataService({ issuer: 'issuer-123' }); spyOn(service, 'findData').and.returnValue(Promise.resolve(null)); const updateSpy = spyOn(service, 'updateData').and.returnValue(Promise.resolve()); const saveSpy = spyOn(service, 'saveData') .and.callFake((data?: DyNTS_LVS_VectorPersist) => Promise.resolve(data as DyNTS_LVS_VectorPersist)); await service.upsertVector({ collectionKey: 'pool-a', vectorId: 'v2', embedding: [0.1, 0.2] }); expect(updateSpy).not.toHaveBeenCalled(); expect(saveSpy).toHaveBeenCalledTimes(1); const written = saveSpy.calls.mostRecent().args[0] as DyNTS_LVS_VectorPersist; expect(written.vectorId).toBe('v2'); expect(written.collectionKey).toBe('pool-a'); expect(written.embedding).toEqual([0.1, 0.2]); expect(written.dimensions).toBe(2); }); }); describe('| removeVector', () => { it('| should absolute-delete the matching record', async () => { const service: DyNTS_LVS_VectorPersist_DataService = new DyNTS_LVS_VectorPersist_DataService({ issuer: 'issuer-123' }); const existing: DyNTS_LVS_VectorPersist = new DyNTS_LVS_VectorPersist({ collectionKey: 'pool-a', vectorId: 'v1', embedding: [1, 0], }); existing._id = 'rec-1'; spyOn(service, 'findData').and.returnValue(Promise.resolve(existing)); const deleteSpy = spyOn(service, 'deleteData').and.returnValue(Promise.resolve()); await service.removeVector('pool-a', 'v1'); expect(deleteSpy).toHaveBeenCalledWith('rec-1', true); }); it('| should be a no-op when no matching record exists', async () => { const service: DyNTS_LVS_VectorPersist_DataService = new DyNTS_LVS_VectorPersist_DataService({ issuer: 'issuer-123' }); spyOn(service, 'findData').and.returnValue(Promise.resolve(null)); const deleteSpy = spyOn(service, 'deleteData').and.returnValue(Promise.resolve()); await service.removeVector('pool-a', 'missing'); expect(deleteSpy).not.toHaveBeenCalled(); }); }); });