hydraNormalizer.test.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. import { vi, describe, test, it, expect, afterEach } from 'vitest'
  2. import type {AnyJson} from "~/types/data";
  3. import HydraNormalizer from "~/services/data/normalizer/hydraNormalizer";
  4. import {METADATA_TYPE} from "~/types/enum/data";
  5. import ApiModel from "~/models/ApiModel";
  6. import {Str, Uid, Attr} from "pinia-orm/dist/decorators";
  7. import {IriEncoded} from "~/models/decorators";
  8. import UrlUtils from "~/services/utils/urlUtils";
  9. import ApiResource from "~/models/ApiResource";
  10. class DummyApiChild extends ApiModel {
  11. static entity = 'dummyChild'
  12. @Uid()
  13. declare id: number | string
  14. }
  15. class DummyApiModel extends ApiModel {
  16. static entity = 'dummyModel'
  17. @Uid()
  18. declare id: number | string
  19. @Attr(null)
  20. @IriEncoded(DummyApiChild)
  21. declare oneToOneRelation: number | null
  22. @Attr([])
  23. @IriEncoded(DummyApiChild)
  24. declare oneToManyRelation: []
  25. @Str(null)
  26. declare name: null
  27. }
  28. afterEach(() => {
  29. vi.clearAllMocks();
  30. vi.resetAllMocks();
  31. });
  32. describe('normalize', () => {
  33. const initialMakeUriMethod = UrlUtils.makeIRI
  34. //@ts-ignore
  35. const initialGetIriEncodedFieldsMethod = HydraNormalizer.getIriEncodedFields
  36. afterEach(() => {
  37. UrlUtils.makeIRI = initialMakeUriMethod
  38. //@ts-ignore
  39. HydraNormalizer.getIriEncodedFields = initialGetIriEncodedFieldsMethod
  40. })
  41. test('should normalize an entity into a JSON Object', () => {
  42. const entity: DummyApiModel = new DummyApiModel({
  43. id: 7351,
  44. oneToOneRelation: 99,
  45. oneToManyRelation: [123, 124, 125]
  46. })
  47. //@ts-ignore
  48. HydraNormalizer.getIriEncodedFields = vi.fn(
  49. (entity) => {
  50. return {
  51. oneToOneRelation: DummyApiChild,
  52. oneToManyRelation: DummyApiChild
  53. }
  54. }
  55. )
  56. //@ts-ignore
  57. UrlUtils.makeIRI = vi.fn(
  58. (targetEntity, id) => {
  59. return {
  60. 99: '/api/dummyChild/99',
  61. 123: '/api/dummyChild/123',
  62. 124: '/api/dummyChild/124',
  63. 125: '/api/dummyChild/125',
  64. }[id]
  65. }
  66. )
  67. const result = HydraNormalizer.normalizeEntity(entity)
  68. const expected = {
  69. id: 7351,
  70. name: null,
  71. oneToOneRelation: '/api/dummyChild/99',
  72. oneToManyRelation: ['/api/dummyChild/123', '/api/dummyChild/124', '/api/dummyChild/125']
  73. }
  74. expect(result).toStrictEqual(expected)
  75. })
  76. })
  77. describe('denormalize', () => {
  78. //@ts-ignore
  79. const initialGetDataMethod = HydraNormalizer.getData
  80. //@ts-ignore
  81. const initialGetMetadataMethod = HydraNormalizer.getMetadata
  82. afterEach(() => {
  83. //@ts-ignore
  84. HydraNormalizer.getData = initialGetDataMethod
  85. //@ts-ignore
  86. HydraNormalizer.getMetadata = initialGetMetadataMethod
  87. })
  88. test('should parse a API Item response and return a JSON Object', () => {
  89. const data: AnyJson = { id: 1, name: 'foo' };
  90. //@ts-ignore
  91. HydraNormalizer.getData = vi.fn((data, model) => {
  92. return data
  93. })
  94. //@ts-ignore
  95. HydraNormalizer.getMetadata = vi.fn((data, model) => {
  96. return { "type": METADATA_TYPE.ITEM }
  97. })
  98. const result = HydraNormalizer.denormalize(data, DummyApiModel)
  99. const expected = {
  100. "data": {
  101. id: 1,
  102. name: 'foo',
  103. },
  104. "metadata": {
  105. "type": METADATA_TYPE.ITEM
  106. }
  107. }
  108. expect(result).toEqual(expected)
  109. })
  110. })
  111. describe('getData', () => {
  112. test('With collection', () => {
  113. const data = {
  114. "@context": "/api/contexts/Foo",
  115. "@id": "/api/foo",
  116. "@type": "hydra:Collection",
  117. "hydra:member": [ 'foo' ],
  118. }
  119. // @ts-ignore
  120. expect(HydraNormalizer.getData(data)).toEqual([ 'foo' ])
  121. })
  122. test('With item', () => {
  123. const data = {
  124. "@context": "/api/contexts/Foo",
  125. "@id": "/api/foo",
  126. "@type": "Foo",
  127. "param1": 'a',
  128. }
  129. // @ts-ignore
  130. expect(HydraNormalizer.getData(data)).toEqual(data)
  131. })
  132. })
  133. describe('getMetadata', () => {
  134. test('With valid collection metadata', () => {
  135. const data = {
  136. "@context": "/api/contexts/Foo",
  137. "@id": "/api/foo",
  138. "@type": "hydra:Collection",
  139. "hydra:member": [ 'foo' ],
  140. "hydra:totalItems": 10,
  141. "hydra:view": {
  142. "@id": "/api/foo?page=3",
  143. "@type": "hydra:PartialCollectionView",
  144. "hydra:first": "/api/foo?page=1",
  145. "hydra:last": "/api/foo?page=5",
  146. "hydra:next": "/api/foo?page=4",
  147. "hydra:previous": "/api/foo?page=2"
  148. }
  149. }
  150. // @ts-ignore
  151. const metadata = HydraNormalizer.getMetadata(data)
  152. expect(metadata.totalItems).toEqual(10)
  153. expect(metadata.firstPage).toEqual(1)
  154. expect(metadata.lastPage).toEqual(5)
  155. expect(metadata.nextPage).toEqual(4)
  156. expect(metadata.previousPage).toEqual(2)
  157. expect(metadata.type).toEqual(METADATA_TYPE.COLLECTION)
  158. })
  159. test('With partial collection metadata', () => {
  160. const data = {
  161. "@context": "/api/contexts/Foo",
  162. "@id": "/api/foo",
  163. "@type": "hydra:Collection",
  164. "hydra:member": [ 'foo' ],
  165. "hydra:totalItems": 10,
  166. "hydra:view": {
  167. "@id": "/api/foo?page=3",
  168. "@type": "hydra:PartialCollectionView",
  169. "hydra:first": "/api/foo?page=1",
  170. }
  171. }
  172. // @ts-ignore
  173. const metadata = HydraNormalizer.getMetadata(data)
  174. expect(metadata.totalItems).toEqual(10)
  175. expect(metadata.firstPage).toEqual(1)
  176. expect(metadata.lastPage).toEqual(1)
  177. expect(metadata.nextPage).toEqual(undefined)
  178. expect(metadata.previousPage).toEqual(undefined)
  179. })
  180. test('With item metadata', () => {
  181. // @ts-ignore
  182. const metadata = HydraNormalizer.getMetadata({})
  183. expect(metadata.type).toEqual(METADATA_TYPE.ITEM)
  184. })
  185. })
  186. describe('denormalizeItem', () => {
  187. //@ts-ignore
  188. const initialDenormalizeEntity = HydraNormalizer.denormalizeEntity
  189. //@ts-ignore
  190. const initialDenormalizeEnum = HydraNormalizer.denormalizeEnum
  191. //@ts-ignore
  192. const initialParseEntityIRI = HydraNormalizer.parseEntityIRI
  193. const initialConsoleError = console.error
  194. afterEach(() => {
  195. //@ts-ignore
  196. HydraNormalizer.denormalizeEntity = initialDenormalizeEntity
  197. //@ts-ignore
  198. HydraNormalizer.denormalizeEnum = initialDenormalizeEnum
  199. //@ts-ignore
  200. HydraNormalizer.parseEntityIRI = initialParseEntityIRI
  201. console.error = initialConsoleError
  202. })
  203. test('With provided model', () => {
  204. const item = {
  205. '@id': '/api/dummyModel/1',
  206. id: 1,
  207. name: 'foo'
  208. }
  209. const model = DummyApiModel
  210. const expected = new DummyApiModel(item)
  211. //@ts-ignore
  212. HydraNormalizer.denormalizeEntity = vi.fn((model, item) => {
  213. return expected
  214. })
  215. //@ts-ignore
  216. const result = HydraNormalizer.denormalizeItem(item, model)
  217. expect(result).toEqual(expected)
  218. //@ts-ignore
  219. expect(HydraNormalizer.denormalizeEntity).toHaveBeenCalledWith(model, item)
  220. })
  221. test('with no @id prop', () => {
  222. const item = {
  223. id: 1,
  224. name: 'foo'
  225. }
  226. console.error = vi.fn((msg) => {})
  227. //@ts-ignore
  228. const result = HydraNormalizer.denormalizeItem(item)
  229. expect(result).toEqual(item)
  230. expect(console.error).toHaveBeenCalledWith(
  231. 'De-normalization error : the item is not hydra formatted',
  232. item
  233. )
  234. })
  235. test('with enum', () => {
  236. const item = {
  237. '@id': '/api/enum/abc',
  238. A: 1,
  239. B: 2,
  240. C: 3
  241. }
  242. //@ts-ignore
  243. HydraNormalizer.denormalizeEnum = vi.fn((item) => {
  244. return item
  245. })
  246. //@ts-ignore
  247. const result = HydraNormalizer.denormalizeItem(item)
  248. expect(result).toEqual(item)
  249. //@ts-ignore
  250. expect(HydraNormalizer.denormalizeEnum).toHaveBeenCalledWith(item)
  251. })
  252. test('with unparsable iri', () => {
  253. const item = {
  254. '@id': 'some_invalid_iri',
  255. id: 1,
  256. name: 'foo'
  257. }
  258. //@ts-ignore
  259. HydraNormalizer.parseEntityIRI = vi.fn((iri) => {
  260. throw('parsing error')
  261. })
  262. console.error = vi.fn((msg) => {})
  263. //@ts-ignore
  264. const result = HydraNormalizer.denormalizeItem(item)
  265. expect(result).toEqual(item)
  266. //@ts-ignore
  267. expect(console.error).toHaveBeenCalledWith(
  268. 'De-normalization error : could not parse the IRI',
  269. item
  270. )
  271. })
  272. test('With valid iri and existing model', () => {
  273. HydraNormalizer.models = {
  274. 'dummyModel': DummyApiModel
  275. }
  276. const item = {
  277. '@id': '/api/dummyModel/1',
  278. id: 1,
  279. name: 'foo'
  280. }
  281. //@ts-ignore
  282. HydraNormalizer.parseEntityIRI = vi.fn((iri) => {
  283. return { entity: 'dummyModel' }
  284. })
  285. const expected = new DummyApiModel(item)
  286. //@ts-ignore
  287. HydraNormalizer.denormalizeEntity = vi.fn((model, item) => {
  288. return expected
  289. })
  290. //@ts-ignore
  291. const result = HydraNormalizer.denormalizeItem(item)
  292. expect(result).toEqual(expected)
  293. //@ts-ignore
  294. expect(HydraNormalizer.denormalizeEntity).toHaveBeenCalledWith(DummyApiModel, item)
  295. })
  296. test('With valid iri and un-existing model', () => {
  297. HydraNormalizer.models = {
  298. 'dummyModel': DummyApiModel
  299. }
  300. const item = {
  301. '@id': '/api/unknownModel/1',
  302. id: 1,
  303. name: 'foo'
  304. }
  305. //@ts-ignore
  306. HydraNormalizer.parseEntityIRI = vi.fn((iri) => {
  307. return 'unknownModel'
  308. })
  309. expect(
  310. //@ts-ignore
  311. () => HydraNormalizer.denormalizeItem(item)
  312. ).toThrowError()
  313. })
  314. })
  315. describe('denormalizeEntity', () => {
  316. //@ts-ignore
  317. const initialGetIriEncodedFieldsMethod = HydraNormalizer.getIriEncodedFields
  318. //@ts-ignore
  319. const initialGetIdFromEntityIriMethod = HydraNormalizer.getIdFromEntityIri
  320. afterEach(() => {
  321. //@ts-ignore
  322. HydraNormalizer.getIriEncodedFields = initialGetIriEncodedFieldsMethod
  323. //@ts-ignore
  324. HydraNormalizer.getIdFromEntityIri = initialGetIdFromEntityIriMethod
  325. })
  326. test('should denormalize a Json object into an entity', () => {
  327. const data = {
  328. id: 7351,
  329. name: null,
  330. oneToOneRelation: '/api/dummyChild/99',
  331. oneToManyRelation: ['/api/dummyChild/123', '/api/dummyChild/124', '/api/dummyChild/125']
  332. }
  333. //@ts-ignore
  334. HydraNormalizer.getIriEncodedFields = vi.fn(
  335. (entity) => {
  336. return {
  337. oneToOneRelation: DummyApiChild,
  338. oneToManyRelation: DummyApiChild
  339. }
  340. }
  341. )
  342. //@ts-ignore
  343. HydraNormalizer.getIdFromEntityIri = vi.fn((iri) => {
  344. return {
  345. '/api/dummyChild/99': 99,
  346. '/api/dummyChild/123': 123,
  347. '/api/dummyChild/124': 124,
  348. '/api/dummyChild/125': 125
  349. }[iri]
  350. })
  351. //@ts-ignore
  352. const result = HydraNormalizer.denormalizeEntity(DummyApiModel, data)
  353. expect(result).toStrictEqual(new DummyApiModel({
  354. id: 7351,
  355. name: null,
  356. oneToOneRelation: 99,
  357. oneToManyRelation: [123, 124, 125]
  358. }))
  359. })
  360. })
  361. describe('denormalizeEnum', () => {
  362. test('does nothing', () => {
  363. const item = {
  364. '@id': '/api/enum/abc',
  365. A: 1,
  366. B: 2,
  367. C: 3
  368. }
  369. //@ts-ignore
  370. expect(HydraNormalizer.denormalizeEnum(item)).toStrictEqual(item)
  371. })
  372. })
  373. describe('parseEntityIRI', () => {
  374. test('valid iri', () => {
  375. const iri = '/api/someEntity/456'
  376. const expected = {
  377. entity: 'someEntity',
  378. id: 456
  379. }
  380. //@ts-ignore
  381. expect(HydraNormalizer.parseEntityIRI(iri)).toStrictEqual(expected)
  382. })
  383. test('invalid iri', () => {
  384. const iri = 'some_invalid_iri'
  385. //@ts-ignore
  386. expect(() => HydraNormalizer.parseEntityIRI(iri)).toThrowError('could not parse the IRI : "some_invalid_iri"')
  387. })
  388. })
  389. describe('getIriEncodedFields', () => {
  390. const initialRelations = DummyApiModel.relations
  391. afterEach(() => {
  392. DummyApiModel.relations = initialRelations
  393. })
  394. test('get relations', () => {
  395. //@ts-ignore
  396. const relations: Record<string, ApiResource> = {'someField': DummyApiChild}
  397. DummyApiModel.relations = relations
  398. const entity = new DummyApiModel()
  399. //@ts-ignore
  400. const results = HydraNormalizer.getIriEncodedFields(entity)
  401. expect(results).toEqual(relations)
  402. })
  403. })
  404. describe('getIdFromEntityIri', () => {
  405. //@ts-ignore
  406. const initialParseEntityIRI = HydraNormalizer.parseEntityIRI
  407. afterEach(() => {
  408. //@ts-ignore
  409. HydraNormalizer.parseEntityIRI = initialParseEntityIRI
  410. })
  411. test('valid iri', () => {
  412. const iri = '/api/someEntity/456'
  413. //@ts-ignore
  414. const result = HydraNormalizer.getIdFromEntityIri(iri, 'someEntity')
  415. expect(result).toEqual(456)
  416. })
  417. test('entity not matching', () => {
  418. const iri = '/api/someEntity/456'
  419. //@ts-ignore
  420. expect(
  421. //@ts-ignore
  422. () => HydraNormalizer.getIdFromEntityIri(iri, 'otherEntity')
  423. ).toThrowError("IRI entity does not match the field's target entity (someEntity != otherEntity)")
  424. })
  425. })