import { DyNTS_DataService } from './data.service'; import { DyNTS_DBService } from './db.service'; import { DyNTS_GlobalService } from '../core/global.service'; import { DyNTS_ArchiveDataService } from './archive-data.service'; import { DyFM_Metadata, DyFM_DataModel_Params, DyFM_Error, DyFM_DBFilter, DyFM_SearchQuery, DyFM_BasicProperty_Type, DyFM_EnvironmentFlag, DyFM_Log } from '@futdevpro/fsm-dynamo'; import { DyNTS_global_settings } from '../../_collections/global-settings.const'; // Initialize global settings before any test runs beforeAll(() => { if (!DyNTS_global_settings.systemShortCodeName) { (DyNTS_global_settings as any).systemShortCodeName = 'TEST'; } if (!DyNTS_global_settings.env_settings) { (DyNTS_global_settings as any).env_settings = { environment: DyFM_EnvironmentFlag.local, }; } }); class TestMetadata extends DyFM_Metadata { name: string; email?: string; userId?: string; } const testDataParams = new DyFM_DataModel_Params({ dataName: 'test_data', properties: { name: { key: 'name', type: DyFM_BasicProperty_Type.string, required: true, }, email: { key: 'email', type: DyFM_BasicProperty_Type.string, }, userId: { key: 'userId', type: DyFM_BasicProperty_Type.string, dependencyDataName: 'user_data', }, }, }); describe('| DyNTS_DataService', () => { let mockDBService: jasmine.SpyObj>; let service: DyNTS_DataService; let testData: TestMetadata; beforeEach(() => { testData = new TestMetadata({ _id: 'test-id-123', name: 'Test Name', email: 'test@example.com', } as TestMetadata); mockDBService = jasmine.createSpyObj('DyNTS_DBService', [ 'getAll', 'getDataById', 'getDataListByIds', 'getDataByDependencyId', 'getDataListByDependencyIds', 'getDataListByDependencyId', 'findOne', 'find', 'updateOne', 'createData', 'modifyData', 'markDeletedById', 'trueDeleteDataById', 'trueDeleteAllData', 'restoreDeletedById', ]); spyOn(DyNTS_GlobalService, 'getDBService').and.returnValue(mockDBService); spyOn(DyNTS_GlobalService, 'getDBServiceByKey').and.returnValue(mockDBService); service = new DyNTS_DataService( testData, testDataParams, 'test-issuer' ); }); it('| should create service instance', () => { expect(service).toBeDefined(); expect(service.data).toBe(testData); expect(service.dataParams).toBe(testDataParams); expect(service.issuer).toBe('test-issuer'); expect(service.dataDBService).toBe(mockDBService); }); it('| should set service name from constructor', () => { expect(service.serviceName).toBe('DyNTS_DataService'); }); it('| should have default error messages', () => { expect(service.defaultErrorUserMsg).toContain('Data Service Error'); expect(service.defaultValidationErrorUserMsg).toContain('Validation Error'); }); it('| should throw error when DB service not found', () => { (DyNTS_GlobalService.getDBService as jasmine.Spy).and.throwError('DB Service not found'); expect(() => { new DyNTS_DataService(testData, testDataParams, 'test-issuer'); }).toThrow(); }); describe('| getAll', () => { it('| should get all data from database', async () => { const mockDataList = [testData, { ...testData, _id: 'test-id-456' }]; mockDBService.getAll.and.returnValue(Promise.resolve(mockDataList)); const result = await service.getAll(); expect(result).toEqual(mockDataList); expect(service.dataList).toEqual(mockDataList); expect(mockDBService.getAll).toHaveBeenCalled(); }); it('| should not set to service when dontSetToService is true', async () => { const mockDataList = [testData]; mockDBService.getAll.and.returnValue(Promise.resolve(mockDataList)); const result = await service.getAll(true); expect(result).toEqual(mockDataList); expect(service.dataList).not.toEqual(mockDataList); }); }); describe('| getDataById', () => { it('| should get data by id', async () => { mockDBService.getDataById.and.returnValue(Promise.resolve(testData)); const result = await service.getDataById('test-id-123'); expect(result).toBe(testData); expect(service.data).toBe(testData); expect(mockDBService.getDataById).toHaveBeenCalledWith('test-id-123'); }); it('| should use data._id when id not provided', async () => { mockDBService.getDataById.and.returnValue(Promise.resolve(testData)); const result = await service.getDataById(); expect(mockDBService.getDataById).toHaveBeenCalledWith('test-id-123'); }); it('| should throw error when id is missing', async () => { service.data = new TestMetadata({}); await expectAsync( service.getDataById() ).toBeRejected(); }); it('| should load from archive when not found and archive enabled', async () => { service.haveArchiveDataService = true; mockDBService.getDataById.and.returnValue(Promise.resolve(null)); const mockArchiveService = jasmine.createSpyObj('DyNTS_ArchiveDataService', ['getDataByOriginalId']); mockArchiveService.getDataByOriginalId.and.returnValue(Promise.resolve(testData)); spyOn(service, 'getArchiveDataService').and.returnValue(mockArchiveService); const result = await service.getDataById('test-id-123'); expect(result).toBe(testData); expect(mockArchiveService.getDataByOriginalId).toHaveBeenCalled(); }); it('| should not load from archive when skipArchiveLoad is true', async () => { service.haveArchiveDataService = true; mockDBService.getDataById.and.returnValue(Promise.resolve(null)); spyOn(service, 'getArchiveDataService'); const result = await service.getDataById('test-id-123', false, true); expect(result).toBeNull(); expect(service.getArchiveDataService).not.toHaveBeenCalled(); }); }); describe('| getDataByIds', () => { it('| should get data by ids', async () => { const ids = ['id-1', 'id-2']; const mockDataList = [testData, { ...testData, _id: 'id-2' }]; mockDBService.find.and.returnValue(Promise.resolve(mockDataList)); const result = await service.getDataByIds(ids); expect(result).toEqual(mockDataList); expect(mockDBService.find).toHaveBeenCalledWith({ _id: { $in: ids } }); }); it('| should throw error when ids is missing', async () => { await expectAsync( service.getDataByIds(null as any) ).toBeRejected(); }); it('| should return empty array when ids is empty array', async () => { const result = await service.getDataByIds([]); expect(result).toEqual([]); }); }); describe('| getDataListByIds', () => { it('| should get data list by ids', async () => { const ids = ['id-1', 'id-2']; const mockDataList = [testData, { ...testData, _id: 'id-2' }]; mockDBService.getDataListByIds.and.returnValue(Promise.resolve(mockDataList)); const result = await service.getDataListByIds(ids); expect(result).toEqual(mockDataList); expect(service.dataList).toEqual(mockDataList); }); }); describe('| getDataByDependencyId', () => { it('| should get data by dependency id', async () => { const dependencyData = { ...testData, userId: 'user-123' }; mockDBService.getDataByDependencyId.and.returnValue(Promise.resolve(dependencyData)); const result = await service.getDataByDependencyId('user-123'); expect(result).toBe(dependencyData); expect(service.data).toBe(dependencyData); }); it('| should throw error when dependency settings not configured', async () => { const serviceWithoutDeps = new DyNTS_DataService( testData, new DyFM_DataModel_Params({ dataName: 'test_data', properties: { name: { key: 'name', type: DyFM_BasicProperty_Type.string } }, }), 'test-issuer' ); await expectAsync( serviceWithoutDeps.getDataByDependencyId('user-123') ).toBeRejected(); }); }); describe('| findData', () => { it('| should find data by filter', async () => { const filter: DyFM_DBFilter = { email: 'test@example.com' }; mockDBService.findOne.and.returnValue(Promise.resolve(testData)); const result = await service.findData(filter); expect(result).toBe(testData); expect(service.data).toBe(testData); expect(mockDBService.findOne).toHaveBeenCalledWith(filter); }); }); describe('| findDataList', () => { it('| should find data list by filter', async () => { const filter: DyFM_DBFilter = { name: 'Test' }; const mockDataList = [testData]; mockDBService.find.and.returnValue(Promise.resolve(mockDataList)); const result = await service.findDataList(filter); expect(result).toEqual(mockDataList); expect(service.dataList).toEqual(mockDataList); }); // FR-258 / SR-3 — over-fetch guard (unbounded find → heap → OOM precursor). Warns, never caps. describe('| over-fetch warning (FR-258 / SR-3)', () => { let originalThreshold: number; beforeEach(() => { originalThreshold = DyNTS_DataService.findDataListWarnThreshold; }); afterEach(() => { DyNTS_DataService.findDataListWarnThreshold = originalThreshold; }); function listOf(count: number): TestMetadata[] { return Array.from({ length: count }, (_v, i: number): TestMetadata => new TestMetadata({ _id: `id-${i}`, name: `n-${i}` } as TestMetadata)); } it('| warns when the result EXCEEDS the threshold', async () => { DyNTS_DataService.findDataListWarnThreshold = 3; const warnSpy = spyOn(DyFM_Log, 'warn'); mockDBService.find.and.returnValue(Promise.resolve(listOf(5))); const result = await service.findDataList({ name: 'Test' }); expect(result.length).toBe(5); const overFetchCall = warnSpy.calls.allArgs().find((args: any[]): boolean => typeof args[0] === 'string' && args[0].includes('[FR-258 SR-3]')); expect(overFetchCall).toBeDefined(); }); it('| does NOT warn at exactly the threshold (strictly greater)', async () => { DyNTS_DataService.findDataListWarnThreshold = 5; const warnSpy = spyOn(DyFM_Log, 'warn'); mockDBService.find.and.returnValue(Promise.resolve(listOf(5))); await service.findDataList({ name: 'Test' }); const overFetchCall = warnSpy.calls.allArgs().find((args: any[]): boolean => typeof args[0] === 'string' && args[0].includes('[FR-258 SR-3]')); expect(overFetchCall).toBeUndefined(); }); it('| disabled (threshold <= 0) → never warns regardless of size', async () => { DyNTS_DataService.findDataListWarnThreshold = 0; const warnSpy = spyOn(DyFM_Log, 'warn'); mockDBService.find.and.returnValue(Promise.resolve(listOf(50000))); await service.findDataList({ name: 'Test' }); const overFetchCall = warnSpy.calls.allArgs().find((args: any[]): boolean => typeof args[0] === 'string' && args[0].includes('[FR-258 SR-3]')); expect(overFetchCall).toBeUndefined(); }); }); }); describe('| updateData', () => { it('| should update data by filterBy', async () => { const filter: DyFM_DBFilter = { email: 'test@example.com' }; const update = { name: 'Updated Name' }; mockDBService.updateOne.and.returnValue(Promise.resolve()); await service.updateData({ filterBy: filter, update }); expect(mockDBService.updateOne).toHaveBeenCalledWith( filter, update, 'test-issuer', undefined ); }); it('| should update data by data._id', async () => { const update = { name: 'Updated Name' }; mockDBService.updateOne.and.returnValue(Promise.resolve()); await service.updateData({ update }); expect(mockDBService.updateOne).toHaveBeenCalledWith( { _id: 'test-id-123' } as any, update, 'test-issuer', undefined ); }); it('| should throw error when no usable parameter provided', async () => { service.data = new TestMetadata({}); const update = { name: 'Updated Name' }; await expectAsync( service.updateData({ update }) ).toBeRejected(); }); }); describe('| saveData', () => { it('| should modify existing data when _id is present', async () => { const existingData = { ...testData, name: 'Updated Name' }; mockDBService.getDataById.and.returnValue(Promise.resolve(testData)); mockDBService.modifyData.and.returnValue(Promise.resolve(existingData)); spyOn(service, 'validateForSave').and.returnValue(Promise.resolve()); const result = await service.saveData(existingData); expect(result).toBe(existingData); expect(mockDBService.modifyData).toHaveBeenCalled(); }); it('| should throw error when data with _id does not exist', async () => { const dataWithId = { ...testData, _id: 'non-existent-id' }; mockDBService.getDataById.and.returnValue(Promise.resolve(null)); spyOn(service, 'validateForSave').and.returnValue(Promise.resolve()); await expectAsync( service.saveData(dataWithId) ).toBeRejected(); }); }); describe('| deleteData', () => { it('| should delete data by id', async () => { mockDBService.getDataById.and.returnValue(Promise.resolve(testData)); mockDBService.markDeletedById.and.returnValue(Promise.resolve()); await service.deleteData('test-id-123'); expect(mockDBService.markDeletedById).toHaveBeenCalledWith('test-id-123', 'test-issuer'); }); it('| should use data._id when id not provided', async () => { mockDBService.getDataById.and.returnValue(Promise.resolve(testData)); mockDBService.markDeletedById.and.returnValue(Promise.resolve()); await service.deleteData(); expect(mockDBService.markDeletedById).toHaveBeenCalledWith('test-id-123', 'test-issuer'); }); it('| should throw error when id is missing', async () => { service.data = new TestMetadata({}); await expectAsync( service.deleteData() ).toBeRejected(); }); it('| should archive data when archive service enabled', async () => { service.haveArchiveDataService = true; mockDBService.getDataById.and.returnValue(Promise.resolve(testData)); mockDBService.trueDeleteDataById.and.returnValue(Promise.resolve()); const mockArchiveService = jasmine.createSpyObj('DyNTS_ArchiveDataService', ['saveData']); mockArchiveService.saveData.and.returnValue(Promise.resolve()); spyOn(service, 'getArchiveDataService').and.returnValue(mockArchiveService); await service.deleteData('test-id-123'); expect(mockDBService.trueDeleteDataById).toHaveBeenCalled(); expect(mockArchiveService.saveData).toHaveBeenCalled(); }); it('| should permanently delete when absolute is true', async () => { mockDBService.getDataById.and.returnValue(Promise.resolve(testData)); mockDBService.trueDeleteDataById.and.returnValue(Promise.resolve()); await service.deleteData('test-id-123', true); expect(mockDBService.trueDeleteDataById).toHaveBeenCalledWith('test-id-123'); }); }); describe('| deleteAllData', () => { it('| should delete all data', async () => { const mockDataList = [testData, { ...testData, _id: 'id-2' }]; mockDBService.getAll.and.returnValue(Promise.resolve(mockDataList)); mockDBService.markDeletedById.and.returnValue(Promise.resolve()); await service.deleteAllData(); expect(mockDBService.markDeletedById).toHaveBeenCalledTimes(2); }); it('| should archive all data when archive service enabled', async () => { service.haveArchiveDataService = true; const mockDataList = [testData]; mockDBService.getAll.and.returnValue(Promise.resolve(mockDataList)); mockDBService.trueDeleteAllData.and.returnValue(Promise.resolve()); const mockArchiveService = jasmine.createSpyObj('DyNTS_ArchiveDataService', ['saveData']); mockArchiveService.saveData.and.returnValue(Promise.resolve()); spyOn(service, 'getArchiveDataService').and.returnValue(mockArchiveService); await service.deleteAllData(); expect(mockDBService.trueDeleteAllData).toHaveBeenCalled(); expect(mockArchiveService.saveData).toHaveBeenCalled(); }); }); describe('| searchData', () => { it('| should search data with query', async () => { const query: DyFM_SearchQuery = { filterBy: { name: 'Test' }, page: 0, pageSize: 10, sortBy: [{ key: 'name', order: 1 }], }; const mockDataList = [testData]; spyOn(service, 'sortAndFilterDataList').and.returnValue(Promise.resolve({ results: mockDataList, totalItems: 1, })); const result = await service.searchData(query); expect(result.results).toEqual(mockDataList); expect(service.sortAndFilterDataList).toHaveBeenCalledWith(query, undefined); }); it('| should apply pagination', async () => { const query: DyFM_SearchQuery = { page: 1, pageSize: 2, }; const mockResults = [ testData, { ...testData, _id: 'id-2' }, { ...testData, _id: 'id-3' }, ]; spyOn(service, 'sortAndFilterDataList').and.returnValue(Promise.resolve({ results: mockResults, totalItems: 3, })); const result = await service.searchData(query); expect(result.results.length).toBe(1); expect(result.results[0]._id).toBe('id-3'); }); }); describe('| getArchiveDataService', () => { it('| should throw error when not implemented', () => { expect(() => { service.getArchiveDataService(); }).toThrow(); }); }); describe('| getDependencyDataDBService', () => { it('| should get dependency DB service when configured', () => { const depService = service.getDependencyDataDBService('user_data'); expect(depService).toBeDefined(); }); it('| should throw error when service key not set', () => { const serviceWithoutDeps = new DyNTS_DataService( testData, new DyFM_DataModel_Params({ dataName: 'test_data', properties: { name: { key: 'name', type: DyFM_BasicProperty_Type.string } }, }), 'test-issuer' ); expect(() => { serviceWithoutDeps.getDependencyDataDBService(); }).toThrow(); }); }); // ════════════════════════════════════════════════════════════════════════ // FR-001 — Generic compareData() // ════════════════════════════════════════════════════════════════════════ describe('| compareData() — FR-001', (): void => { let svc: DyNTS_DataService; beforeEach((): void => { // Re-use existing TestMetadata + testDataParams. const t: TestMetadata = new TestMetadata(); svc = new DyNTS_DataService(t, testDataParams, 'test-issuer'); }); it('| equal POJO returns { result: "equal" } — sin changedFields', (): void => { const a: TestMetadata = Object.assign(new TestMetadata(), { name: 'alice', email: 'a@x' }); const b: TestMetadata = Object.assign(new TestMetadata(), { name: 'alice', email: 'a@x' }); const r = svc.compareData(a, b); expect(r.result).toBe('equal'); expect(r.changedFields).toBeUndefined(); }); it('| modified single field — changedFields tartalmazza azt', (): void => { const a: TestMetadata = Object.assign(new TestMetadata(), { name: 'alice', email: 'a@x' }); const b: TestMetadata = Object.assign(new TestMetadata(), { name: 'bob', email: 'a@x' }); const r = svc.compareData(a, b); expect(r.result).toBe('modified'); expect(r.changedFields).toEqual(['name']); }); it('| modified multi-field — changedFields TELJES listat ad (nem early-return)', (): void => { const a: TestMetadata = Object.assign(new TestMetadata(), { name: 'alice', email: 'a@x', userId: 'u1' }); const b: TestMetadata = Object.assign(new TestMetadata(), { name: 'bob', email: 'b@y', userId: 'u2' }); const r = svc.compareData(a, b); expect(r.result).toBe('modified'); expect(r.changedFields?.sort()).toEqual(['email', 'name', 'userId'] as any); }); it('| auto-discovery SKIP-eli a metadata fields-et (_id, __created, __lastModified)', (): void => { const now: Date = new Date(); const a: TestMetadata = Object.assign(new TestMetadata(), { name: 'alice', _id: 'X', __created: now, __lastModified: now }); const b: TestMetadata = Object.assign(new TestMetadata(), { name: 'alice', _id: 'Y', __created: new Date(2000, 0, 1), __lastModified: new Date(2001, 0, 1) }); const r = svc.compareData(a, b); expect(r.result).toBe('equal'); }); it('| explicit fields override — KIFEJEZETT _id-val VIZSGALJA a skip-listet is', (): void => { const a: TestMetadata = Object.assign(new TestMetadata(), { name: 'alice', _id: 'X' }); const b: TestMetadata = Object.assign(new TestMetadata(), { name: 'alice', _id: 'Y' }); const r = svc.compareData(a, b, { fields: ['_id' as keyof TestMetadata] }); expect(r.result).toBe('modified'); expect(r.changedFields).toEqual(['_id' as keyof TestMetadata]); }); it('| explicit fields scope-szukit — csak a listazott fields szamit', (): void => { const a: TestMetadata = Object.assign(new TestMetadata(), { name: 'alice', email: 'a@x' }); const b: TestMetadata = Object.assign(new TestMetadata(), { name: 'alice', email: 'b@y' }); const r = svc.compareData(a, b, { fields: ['name'] }); expect(r.result).toBe('equal'); // email change ignored, only 'name' checked }); it('| customComparator override (case-insensitive string)', (): void => { const a: TestMetadata = Object.assign(new TestMetadata(), { name: 'Alice' }); const b: TestMetadata = Object.assign(new TestMetadata(), { name: 'alice' }); const r = svc.compareData(a, b, { customComparators: { name: (x: string, y: string) => x.toLowerCase() === y.toLowerCase() }, }); expect(r.result).toBe('equal'); }); it('| customComparator — array-set-equality eltero sorrendre', (): void => { class WithArr extends DyFM_Metadata { name?: string; tags?: string[]; } const arrSvc = new DyNTS_DataService( new WithArr(), new DyFM_DataModel_Params({ dataName: 'with_arr', properties: { name: { key: 'name', type: DyFM_BasicProperty_Type.string } }, }), 'test-issuer', ); const a: WithArr = Object.assign(new WithArr(), { name: 'x', tags: ['a', 'b', 'c'] }); const b: WithArr = Object.assign(new WithArr(), { name: 'x', tags: ['c', 'b', 'a'] }); const r = arrSvc.compareData(a, b, { customComparators: { tags: (x: string[], y: string[]) => { const s: Set = new Set(x); return y.length === x.length && y.every((v: string) => s.has(v)); }, }, }); expect(r.result).toBe('equal'); }); it('| deep-equal: Date instances ugyanazon time-stamp-pel equal-nek szamitanak', (): void => { class WithDate extends DyFM_Metadata { when?: Date; } const dateSvc = new DyNTS_DataService( new WithDate(), new DyFM_DataModel_Params({ dataName: 'with_date', properties: {}, }), 'test-issuer', ); const t: number = 1_700_000_000_000; const a: WithDate = Object.assign(new WithDate(), { when: new Date(t) }); const b: WithDate = Object.assign(new WithDate(), { when: new Date(t) }); expect(dateSvc.compareData(a, b).result).toBe('equal'); }); it('| deep-equal: nested object', (): void => { class WithNested extends DyFM_Metadata { nested?: { a: number; b: { c: string } }; } const nSvc = new DyNTS_DataService( new WithNested(), new DyFM_DataModel_Params({ dataName: 'with_nested', properties: {} }), 'test-issuer', ); const a: WithNested = Object.assign(new WithNested(), { nested: { a: 1, b: { c: 'x' } } }); const b: WithNested = Object.assign(new WithNested(), { nested: { a: 1, b: { c: 'x' } } }); const c: WithNested = Object.assign(new WithNested(), { nested: { a: 1, b: { c: 'y' } } }); expect(nSvc.compareData(a, b).result).toBe('equal'); expect(nSvc.compareData(a, c).result).toBe('modified'); }); it('| deep-equal: array index-szerinti compare (eltero sorrend → modified default-ban)', (): void => { class WithArr2 extends DyFM_Metadata { items?: number[]; } const aSvc = new DyNTS_DataService( new WithArr2(), new DyFM_DataModel_Params({ dataName: 'with_arr2', properties: {} }), 'test-issuer', ); const a: WithArr2 = Object.assign(new WithArr2(), { items: [1, 2, 3] }); const b: WithArr2 = Object.assign(new WithArr2(), { items: [1, 2, 3] }); const c: WithArr2 = Object.assign(new WithArr2(), { items: [3, 2, 1] }); expect(aSvc.compareData(a, b).result).toBe('equal'); expect(aSvc.compareData(a, c).result).toBe('modified'); }); it('| throw 400 ha newData null', (): void => { const b: TestMetadata = Object.assign(new TestMetadata(), { name: 'x' }); let thrown: any = null; try { svc.compareData(null as any, b); } catch (e) { thrown = e; } expect(thrown).not.toBeNull(); expect(DyFM_Error.getErrorStatus(thrown)).toBe(400); expect(DyFM_Error.getErrorCode(thrown)).toContain('DyNTS-DS0-CD1'); }); it('| throw 400 ha oldData undefined', (): void => { const a: TestMetadata = Object.assign(new TestMetadata(), { name: 'x' }); let thrown: any = null; try { svc.compareData(a, undefined as any); } catch (e) { thrown = e; } expect(thrown).not.toBeNull(); expect(DyFM_Error.getErrorStatus(thrown)).toBe(400); }); it('| throw 400 ha options.fields ures array', (): void => { const a: TestMetadata = Object.assign(new TestMetadata(), { name: 'alice' }); const b: TestMetadata = Object.assign(new TestMetadata(), { name: 'alice' }); let thrown: any = null; try { svc.compareData(a, b, { fields: [] }); } catch (e) { thrown = e; } expect(thrown).not.toBeNull(); expect(DyFM_Error.getErrorStatus(thrown)).toBe(400); expect(DyFM_Error.getErrorCode(thrown)).toContain('DyNTS-DS0-CD2'); }); it('| asymmetric keys: ha az egyik objektum-bol hianyzik a key, modified-nek szamit', (): void => { const a: TestMetadata = Object.assign(new TestMetadata(), { name: 'alice', email: 'a@x' }); const b: TestMetadata = Object.assign(new TestMetadata(), { name: 'alice' }); // no email const r = svc.compareData(a, b); expect(r.result).toBe('modified'); expect(r.changedFields).toEqual(['email']); }); }); });