hydraNormalizer.test.ts 14 KB

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