import { vi, describe, test, it, expect, afterEach } from 'vitest' import type {AnyJson} from "~/types/data"; import HydraNormalizer from "~/services/data/normalizer/hydraNormalizer"; import {METADATA_TYPE} from "~/types/enum/data"; import ApiModel from "~/models/ApiModel"; import {Str, Uid, Attr} from "pinia-orm/dist/decorators"; import {IriEncoded} from "~/models/decorators"; import UrlUtils from "~/services/utils/urlUtils"; import ApiResource from "~/models/ApiResource"; class DummyApiChild extends ApiModel { static entity = 'dummyChild' @Uid() declare id: number | string } class DummyApiModel extends ApiModel { static entity = 'dummyModel' @Uid() declare id: number | string @Attr(null) @IriEncoded(DummyApiChild) declare oneToOneRelation: number | null @Attr([]) @IriEncoded(DummyApiChild) declare oneToManyRelation: [] @Str(null) declare name: null } afterEach(() => { vi.clearAllMocks(); vi.resetAllMocks(); }); describe('normalize', () => { const initialMakeUriMethod = UrlUtils.makeIRI afterEach(() => { UrlUtils.makeIRI = initialMakeUriMethod }) test('should normalize an entity into a JSON Object', () => { const entity: DummyApiModel = new DummyApiModel({ id: 7351, oneToOneRelation: 99, oneToManyRelation: [123, 124, 125] }) //@ts-ignore UrlUtils.makeIRI = vi.fn( (targetEntity, id) => { return { 99: '/api/dummyChild/99', 123: '/api/dummyChild/123', 124: '/api/dummyChild/124', 125: '/api/dummyChild/125', }[id] } ) const result = HydraNormalizer.normalizeEntity(entity) const expected = { id: 7351, name: null, oneToOneRelation: '/api/dummyChild/99', oneToManyRelation: ['/api/dummyChild/123', '/api/dummyChild/124', '/api/dummyChild/125'] } expect(result).toStrictEqual(expected) }) }) describe('denormalize', () => { //@ts-ignore const initialGetDataMethod = HydraNormalizer.getData //@ts-ignore const initialGetMetadataMethod = HydraNormalizer.getMetadata afterEach(() => { //@ts-ignore HydraNormalizer.getData = initialGetDataMethod //@ts-ignore HydraNormalizer.getMetadata = initialGetMetadataMethod }) test('should parse a API Item response and return a JSON Object', () => { const data: AnyJson = { id: 1, name: 'foo' }; //@ts-ignore HydraNormalizer.getData = vi.fn((data, model) => { return data }) //@ts-ignore HydraNormalizer.getMetadata = vi.fn((data, model) => { return { "type": METADATA_TYPE.ITEM } }) const result = HydraNormalizer.denormalize(data, DummyApiModel) const expected = { "data": { id: 1, name: 'foo', }, "metadata": { "type": METADATA_TYPE.ITEM } } expect(result).toEqual(expected) }) }) describe('getData', () => { test('With collection', () => { const data = { "@context": "/api/contexts/Foo", "@id": "/api/foo", "@type": "hydra:Collection", "hydra:member": [ 'foo' ], } // @ts-ignore expect(HydraNormalizer.getData(data)).toEqual([ 'foo' ]) }) test('With item', () => { const data = { "@context": "/api/contexts/Foo", "@id": "/api/foo", "@type": "Foo", "param1": 'a', } // @ts-ignore expect(HydraNormalizer.getData(data)).toEqual(data) }) }) describe('getMetadata', () => { test('With valid collection metadata', () => { const data = { "@context": "/api/contexts/Foo", "@id": "/api/foo", "@type": "hydra:Collection", "hydra:member": [ 'foo' ], "hydra:totalItems": 10, "hydra:view": { "@id": "/api/foo?page=3", "@type": "hydra:PartialCollectionView", "hydra:first": "/api/foo?page=1", "hydra:last": "/api/foo?page=5", "hydra:next": "/api/foo?page=4", "hydra:previous": "/api/foo?page=2" } } // @ts-ignore const metadata = HydraNormalizer.getMetadata(data) expect(metadata.totalItems).toEqual(10) expect(metadata.firstPage).toEqual(1) expect(metadata.lastPage).toEqual(5) expect(metadata.nextPage).toEqual(4) expect(metadata.previousPage).toEqual(2) expect(metadata.type).toEqual(METADATA_TYPE.COLLECTION) }) test('With partial collection metadata', () => { const data = { "@context": "/api/contexts/Foo", "@id": "/api/foo", "@type": "hydra:Collection", "hydra:member": [ 'foo' ], "hydra:totalItems": 10, "hydra:view": { "@id": "/api/foo?page=3", "@type": "hydra:PartialCollectionView", "hydra:first": "/api/foo?page=1", } } // @ts-ignore const metadata = HydraNormalizer.getMetadata(data) expect(metadata.totalItems).toEqual(10) expect(metadata.firstPage).toEqual(1) expect(metadata.lastPage).toEqual(1) expect(metadata.nextPage).toEqual(undefined) expect(metadata.previousPage).toEqual(undefined) }) test('With item metadata', () => { // @ts-ignore const metadata = HydraNormalizer.getMetadata({}) expect(metadata.type).toEqual(METADATA_TYPE.ITEM) }) }) describe('denormalizeItem', () => { //@ts-ignore const initialDenormalizeEntity = HydraNormalizer.denormalizeEntity //@ts-ignore const initialDenormalizeEnum = HydraNormalizer.denormalizeEnum //@ts-ignore const initialParseEntityIRI = HydraNormalizer.parseEntityIRI const initialConsoleError = console.error afterEach(() => { //@ts-ignore HydraNormalizer.denormalizeEntity = initialDenormalizeEntity //@ts-ignore HydraNormalizer.denormalizeEnum = initialDenormalizeEnum //@ts-ignore HydraNormalizer.parseEntityIRI = initialParseEntityIRI console.error = initialConsoleError }) test('With provided model', () => { const item = { '@id': '/api/dummyModel/1', id: 1, name: 'foo' } const model = DummyApiModel const expected = new DummyApiModel(item) //@ts-ignore HydraNormalizer.denormalizeEntity = vi.fn((model, item) => { return expected }) //@ts-ignore const result = HydraNormalizer.denormalizeItem(item, model) expect(result).toEqual(expected) //@ts-ignore expect(HydraNormalizer.denormalizeEntity).toHaveBeenCalledWith(model, item) }) test('with no @id prop', () => { const item = { id: 1, name: 'foo' } console.error = vi.fn((msg) => {}) //@ts-ignore const result = HydraNormalizer.denormalizeItem(item) expect(result).toEqual(item) expect(console.error).toHaveBeenCalledWith( 'De-normalization error : the item is not hydra formatted', item ) }) test('with enum', () => { const item = { '@id': '/api/enum/abc', A: 1, B: 2, C: 3 } //@ts-ignore HydraNormalizer.denormalizeEnum = vi.fn((item) => { return item }) //@ts-ignore const result = HydraNormalizer.denormalizeItem(item) expect(result).toEqual(item) //@ts-ignore expect(HydraNormalizer.denormalizeEnum).toHaveBeenCalledWith(item) }) test('with unparsable iri', () => { const item = { '@id': 'some_invalid_iri', id: 1, name: 'foo' } //@ts-ignore HydraNormalizer.parseEntityIRI = vi.fn((iri) => { throw('parsing error') }) console.error = vi.fn((msg) => {}) //@ts-ignore const result = HydraNormalizer.denormalizeItem(item) expect(result).toEqual(item) //@ts-ignore expect(console.error).toHaveBeenCalledWith( 'De-normalization error : could not parse the IRI', item ) }) test('With valid iri and existing model', () => { HydraNormalizer.models = { 'dummyModel': DummyApiModel } const item = { '@id': '/api/dummyModel/1', id: 1, name: 'foo' } //@ts-ignore HydraNormalizer.parseEntityIRI = vi.fn((iri) => { return { entity: 'dummyModel' } }) const expected = new DummyApiModel(item) //@ts-ignore HydraNormalizer.denormalizeEntity = vi.fn((model, item) => { return expected }) //@ts-ignore const result = HydraNormalizer.denormalizeItem(item) expect(result).toEqual(expected) //@ts-ignore expect(HydraNormalizer.denormalizeEntity).toHaveBeenCalledWith(DummyApiModel, item) }) test('With valid iri and un-existing model', () => { HydraNormalizer.models = { 'dummyModel': DummyApiModel } const item = { '@id': '/api/unknownModel/1', id: 1, name: 'foo' } //@ts-ignore HydraNormalizer.parseEntityIRI = vi.fn((iri) => { return 'unknownModel' }) expect( //@ts-ignore () => HydraNormalizer.denormalizeItem(item) ).toThrowError() }) }) describe('denormalizeEntity', () => { //@ts-ignore const initialGetIdFromEntityIriMethod = HydraNormalizer.getIdFromEntityIri afterEach(() => { //@ts-ignore HydraNormalizer.getIdFromEntityIri = initialGetIdFromEntityIriMethod }) test('should denormalize a Json object into an entity', () => { const data = { id: 7351, name: null, oneToOneRelation: '/api/dummyChild/99', oneToManyRelation: ['/api/dummyChild/123', '/api/dummyChild/124', '/api/dummyChild/125'] } //@ts-ignore HydraNormalizer.getIdFromEntityIri = vi.fn((iri) => { return { '/api/dummyChild/99': 99, '/api/dummyChild/123': 123, '/api/dummyChild/124': 124, '/api/dummyChild/125': 125 }[iri] }) //@ts-ignore const result = HydraNormalizer.denormalizeEntity(DummyApiModel, data) expect(result).toStrictEqual(new DummyApiModel({ id: 7351, name: null, oneToOneRelation: 99, oneToManyRelation: [123, 124, 125] })) }) test('should ignore relations fields when missing in the incoming data', () => { const data = { id: 7351, name: 'Bob', oneToManyRelation: ['/api/dummyChild/123', '/api/dummyChild/124', '/api/dummyChild/125'] } //@ts-ignore HydraNormalizer.getIdFromEntityIri = vi.fn((iri) => { return { '/api/dummyChild/123': 123, '/api/dummyChild/124': 124, '/api/dummyChild/125': 125 }[iri] }) //@ts-ignore const result = HydraNormalizer.denormalizeEntity(DummyApiModel, data) expect(result).toStrictEqual(new DummyApiModel({ id: 7351, name: "Bob", oneToOneRelation: null, oneToManyRelation: [123, 124, 125] })) }) test('should handle relations with empty values', () => { const data = { id: 7351, name: null, oneToOneRelation: null, oneToManyRelation: [] } //@ts-ignore HydraNormalizer.getIriEncodedFields = vi.fn( (entity) => { return { oneToOneRelation: DummyApiChild, oneToManyRelation: DummyApiChild } } ) //@ts-ignore const result = HydraNormalizer.denormalizeEntity(DummyApiModel, data) expect(result).toStrictEqual(new DummyApiModel({ id: 7351, name: null, oneToOneRelation: null, oneToManyRelation: [] })) }) }) describe('denormalizeEnum', () => { test('does nothing', () => { const item = { '@id': '/api/enum/abc', A: 1, B: 2, C: 3 } //@ts-ignore expect(HydraNormalizer.denormalizeEnum(item)).toStrictEqual(item) }) }) describe('parseEntityIRI', () => { test('valid iri', () => { const iri = '/api/someEntity/456' const expected = { entity: 'someEntity', id: 456 } //@ts-ignore expect(HydraNormalizer.parseEntityIRI(iri)).toStrictEqual(expected) }) test('invalid iri', () => { const iri = 'some_invalid_iri' //@ts-ignore expect(() => HydraNormalizer.parseEntityIRI(iri)).toThrowError('could not parse the IRI : "some_invalid_iri"') }) }) describe('getIdFromEntityIri', () => { //@ts-ignore const initialParseEntityIRI = HydraNormalizer.parseEntityIRI afterEach(() => { //@ts-ignore HydraNormalizer.parseEntityIRI = initialParseEntityIRI }) test('valid iri', () => { const iri = '/api/someEntity/456' //@ts-ignore const result = HydraNormalizer.getIdFromEntityIri(iri, 'someEntity') expect(result).toEqual(456) }) test('entity not matching', () => { const iri = '/api/someEntity/456' //@ts-ignore expect( //@ts-ignore () => HydraNormalizer.getIdFromEntityIri(iri, 'otherEntity') ).toThrowError("IRI entity does not match the field's target entity (someEntity != otherEntity)") }) })