|
|
@@ -0,0 +1,532 @@
|
|
|
+import { vi, describe, test, it, expect, afterEach } from 'vitest'
|
|
|
+import {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
|
|
|
+ //@ts-ignore
|
|
|
+ const initialGetIriEncodedFieldsMethod = HydraNormalizer.getIriEncodedFields
|
|
|
+
|
|
|
+ afterEach(() => {
|
|
|
+ UrlUtils.makeIRI = initialMakeUriMethod
|
|
|
+ //@ts-ignore
|
|
|
+ HydraNormalizer.getIriEncodedFields = initialGetIriEncodedFieldsMethod
|
|
|
+ })
|
|
|
+
|
|
|
+ test('should normalize an entity into a JSON Object', () => {
|
|
|
+
|
|
|
+ const entity: DummyApiModel = new DummyApiModel({
|
|
|
+ id: 7351,
|
|
|
+ oneToOneRelation: 99,
|
|
|
+ oneToManyRelation: [123, 124, 125]
|
|
|
+ })
|
|
|
+
|
|
|
+ //@ts-ignore
|
|
|
+ HydraNormalizer.getIriEncodedFields = vi.fn(
|
|
|
+ (entity) => {
|
|
|
+ return {
|
|
|
+ oneToOneRelation: DummyApiChild,
|
|
|
+ oneToManyRelation: DummyApiChild
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ //@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 initialGetIriEncodedFieldsMethod = HydraNormalizer.getIriEncodedFields
|
|
|
+ //@ts-ignore
|
|
|
+ const initialGetIdFromEntityIriMethod = HydraNormalizer.getIdFromEntityIri
|
|
|
+
|
|
|
+ afterEach(() => {
|
|
|
+ //@ts-ignore
|
|
|
+ HydraNormalizer.getIriEncodedFields = initialGetIriEncodedFieldsMethod
|
|
|
+ //@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.getIriEncodedFields = vi.fn(
|
|
|
+ (entity) => {
|
|
|
+ return {
|
|
|
+ oneToOneRelation: DummyApiChild,
|
|
|
+ oneToManyRelation: DummyApiChild
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ //@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]
|
|
|
+ }))
|
|
|
+
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+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('getIriEncodedFields', () => {
|
|
|
+ const initialRelations = DummyApiModel.relations
|
|
|
+
|
|
|
+ afterEach(() => {
|
|
|
+ DummyApiModel.relations = initialRelations
|
|
|
+ })
|
|
|
+
|
|
|
+ test('get relations', () => {
|
|
|
+ //@ts-ignore
|
|
|
+ const relations: Record<string, ApiResource> = {'someField': DummyApiChild}
|
|
|
+
|
|
|
+ DummyApiModel.relations = relations
|
|
|
+
|
|
|
+ const entity = new DummyApiModel()
|
|
|
+
|
|
|
+ //@ts-ignore
|
|
|
+ const results = HydraNormalizer.getIriEncodedFields(entity)
|
|
|
+
|
|
|
+ expect(results).toEqual(relations)
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+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)")
|
|
|
+ })
|
|
|
+
|
|
|
+
|
|
|
+})
|