entityManager.test.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994
  1. import { describe, test, vi, expect, beforeEach, afterEach } from 'vitest'
  2. import type { Repository, Element } from 'pinia-orm'
  3. import { Str, Uid } from 'pinia-orm/dist/decorators'
  4. import EntityManager from '~/services/data/entityManager'
  5. import ApiResource from '~/models/ApiResource'
  6. import ApiModel from '~/models/ApiModel'
  7. import type ApiRequestService from '~/services/data/apiRequestService'
  8. import { IdField } from '~/models/decorators'
  9. class DummyApiResource extends ApiResource {
  10. static entity = 'dummyResource'
  11. @Uid()
  12. declare id: number | string
  13. @Str(null)
  14. declare name: string
  15. }
  16. class DummyApiModel extends ApiModel {
  17. static entity = 'dummyModel'
  18. @Uid()
  19. declare id: number | string
  20. @Str(null)
  21. declare name: string
  22. }
  23. class TestableEntityManager extends EntityManager {
  24. public _getProfileMask: () => object
  25. public removeTempAfterPersist(
  26. model: typeof ApiResource,
  27. tempInstanceId: number | string,
  28. ) {
  29. return super.removeTempAfterPersist(model, tempInstanceId)
  30. }
  31. public makeProfileHash() {
  32. return super.makeProfileHash()
  33. }
  34. public validateEntity(instance: unknown): void {
  35. return super.validateEntity(instance)
  36. }
  37. }
  38. const _console: any = {
  39. log: console.log,
  40. warn: console.warn,
  41. error: console.error,
  42. }
  43. vi.mock('~/models/models', async () => {
  44. class MyModel {
  45. static entity = 'my-model'
  46. }
  47. const modelsIndex: Record<string, any> = { 'my-model': () => MyModel }
  48. return {
  49. default: modelsIndex,
  50. }
  51. })
  52. let apiRequestService: ApiRequestService
  53. let entityManager: TestableEntityManager
  54. let repo: Repository<ApiResource>
  55. let _getRepo: (model: typeof ApiResource) => Repository<ApiResource>
  56. let _getProfileMask: () => object
  57. beforeEach(() => {
  58. // @ts-ignore
  59. repo = vi.fn() as Repository<ApiResource>
  60. // @ts-ignore
  61. apiRequestService = vi.fn() as ApiRequestService
  62. _getRepo = vi.fn((model: typeof ApiResource) => repo)
  63. _getProfileMask = vi.fn(() => {
  64. return {}
  65. })
  66. entityManager = new TestableEntityManager(
  67. apiRequestService,
  68. _getRepo,
  69. _getProfileMask,
  70. )
  71. })
  72. afterEach(() => {
  73. // Reset console methods after mock
  74. console.log = _console.log
  75. console.warn = _console.warn
  76. console.error = _console.error
  77. })
  78. describe('getRepository', () => {
  79. test('simple call', () => {
  80. entityManager.getRepository(DummyApiResource)
  81. expect(_getRepo).toHaveBeenCalledWith(DummyApiResource)
  82. })
  83. })
  84. describe('getQuery', () => {
  85. test('simple call', () => {
  86. // @ts-ignore
  87. const repo = vi.fn()
  88. // @ts-ignore
  89. entityManager.getRepository = vi.fn(() => repo)
  90. const query = vi.fn()
  91. // @ts-ignore
  92. repo.where = vi.fn((method) => {
  93. // Query method is supposed to exclude NaN ids values
  94. expect(method({ id: 123 })).toBeTruthy()
  95. expect(method({ id: 'abc' })).toBeFalsy()
  96. return query
  97. })
  98. const result = entityManager.getQuery(DummyApiResource)
  99. expect(result).toEqual(query)
  100. })
  101. })
  102. describe('cast', () => {
  103. test('simple cast', () => {
  104. // @ts-ignore
  105. const result = entityManager.cast(DummyApiResource, { id: 1 })
  106. expect(result instanceof DummyApiResource).toEqual(true)
  107. })
  108. })
  109. describe('getModelFor', () => {
  110. test('simple call', async () => {
  111. const model = await entityManager.getModelFor('my-model')
  112. expect(model!.entity).toEqual('my-model')
  113. })
  114. test('non existing model', async () => {
  115. expect(
  116. async () => await entityManager.getModelFor('non-existing-model'),
  117. ).rejects.toThrowError(
  118. "No model found for entity name 'non-existing-model'",
  119. )
  120. })
  121. })
  122. describe('getModelFromIri', () => {
  123. test('simple call', async () => {
  124. // @ts-ignore
  125. entityManager.getModelFor = vi.fn(
  126. async (entityName: string) => DummyApiResource,
  127. )
  128. // @ts-ignore
  129. const result = await entityManager.getModelFromIri('/api/dummy/123')
  130. console.log(result)
  131. expect(result).toEqual(DummyApiResource)
  132. })
  133. test('invalide Iri', () => {
  134. expect(
  135. async () => await entityManager.getModelFromIri('/invalid'),
  136. ).rejects.toThrowError('cannot parse the IRI')
  137. })
  138. })
  139. describe('newInstance', () => {
  140. test('simple call', () => {
  141. const properties = { id: 1 }
  142. // @ts-ignore
  143. entityManager.getRepository = vi.fn((model: typeof ApiResource) => {
  144. return model === DummyApiResource ? repo : null
  145. })
  146. // @ts-ignore
  147. const entity = new DummyApiResource(properties)
  148. // @ts-ignore
  149. repo.make = vi.fn((properties: object) => {
  150. // @ts-ignore
  151. entity.id = properties.id
  152. return entity
  153. })
  154. // @ts-ignore
  155. entityManager.save = vi.fn(
  156. (entity: ApiResource, permanent: boolean) => entity,
  157. )
  158. const result = entityManager.newInstance(DummyApiResource, properties)
  159. expect(repo.make).toHaveBeenCalledWith(properties)
  160. expect(entityManager.save).toHaveBeenCalledWith(entity, true)
  161. expect(result.id).toEqual(properties.id)
  162. })
  163. test('with no id provided', () => {
  164. const properties = { name: 'bob' }
  165. // @ts-ignore
  166. const repo = vi.fn() as Repository<ApiResource>
  167. // @ts-ignore
  168. entityManager.getRepository = vi.fn((model: typeof ApiResource) => {
  169. return model === DummyApiResource ? repo : null
  170. })
  171. // @ts-ignore
  172. entityManager.saveInitialState = vi.fn(
  173. (model: typeof ApiResource, entity: ApiResource) => null,
  174. )
  175. // @ts-ignore
  176. const entity = new DummyApiResource(properties)
  177. // @ts-ignore
  178. repo.make = vi.fn((properties: object) => {
  179. // @ts-ignore
  180. entity.name = properties.name
  181. return entity
  182. })
  183. // @ts-ignore
  184. repo.save = vi.fn((record: Element) => entity)
  185. const result = entityManager.newInstance(DummyApiResource, properties)
  186. expect(result.id, "id is 'tmp' followed by a valid uuid-V4").toMatch(
  187. /tmp[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}/,
  188. )
  189. expect(result.name).toEqual(properties.name)
  190. })
  191. })
  192. describe('save', () => {
  193. test('simple call', () => {
  194. // @ts-ignore
  195. const repo = vi.fn() as Repository<ApiResource>
  196. // @ts-ignore
  197. entityManager.getRepository = vi.fn((model: typeof ApiResource) => {
  198. return model === DummyApiResource ? repo : null
  199. })
  200. entityManager.validateEntity = vi.fn((instance: unknown) => {})
  201. // @ts-ignore
  202. repo.save = vi.fn((record: Element) => entity)
  203. const entity = new DummyApiResource({ id: 1 })
  204. entityManager.save(entity)
  205. expect(entityManager.validateEntity).toHaveBeenCalledWith(entity)
  206. expect(repo.save).toHaveBeenCalledWith(entity)
  207. })
  208. })
  209. describe('find', () => {
  210. test('simple call', () => {
  211. // @ts-ignore
  212. const repo = vi.fn() as Repository<ApiResource>
  213. // @ts-ignore
  214. entityManager.getRepository = vi.fn((model: typeof ApiResource) => {
  215. return model === DummyApiResource ? repo : null
  216. })
  217. const entity = new DummyApiResource({ id: 1 })
  218. // @ts-ignore
  219. repo.find = vi.fn((id: string | number) => entity)
  220. entityManager.find(DummyApiResource, 1)
  221. expect(entityManager.getRepository).toHaveBeenCalledWith(DummyApiResource)
  222. expect(repo.find).toHaveBeenCalledWith(1)
  223. })
  224. })
  225. describe('fetch', () => {
  226. test('fetch model', async () => {
  227. const properties = { id: 1 }
  228. const entity = new DummyApiResource({ id: 1 })
  229. // @ts-ignore
  230. apiRequestService.get = vi.fn(async (url: string) => properties)
  231. // @ts-ignore
  232. entityManager.newInstance = vi.fn(
  233. <T extends typeof ApiResource>(model: T, props: object) => entity,
  234. )
  235. const result = await entityManager.fetch(DummyApiResource, 1)
  236. expect(apiRequestService.get).toHaveBeenCalledWith('api/dummyResource/1')
  237. expect(entityManager.newInstance).toHaveBeenCalledWith(DummyApiResource, {
  238. id: 1,
  239. name: null,
  240. _model: undefined,
  241. })
  242. expect(result).toEqual(entity)
  243. })
  244. })
  245. describe('fetchCollection', () => {
  246. test('simple call', async () => {
  247. const collection = {
  248. '@type': 'Collection',
  249. totalItems: 3,
  250. member: [{ id: 1 }, { id: 2 }, { id: 3 }],
  251. }
  252. // @ts-ignore
  253. apiRequestService.get = vi.fn(async (url: string) => collection)
  254. // @ts-ignore
  255. entityManager.newInstance = vi.fn(
  256. (model: typeof ApiResource, props: object) => {
  257. return new DummyApiResource(props)
  258. },
  259. )
  260. const piniaOrmQuery = vi.fn()
  261. // @ts-ignore
  262. entityManager.getQuery = vi.fn((model: typeof ApiResource) => piniaOrmQuery)
  263. const result = await entityManager.fetchCollection(DummyApiResource, null)
  264. expect(apiRequestService.get).toHaveBeenCalledWith('api/dummyResource')
  265. expect(entityManager.newInstance).toHaveBeenCalledTimes(3)
  266. expect(entityManager.newInstance).toHaveBeenCalledWith(DummyApiResource, {
  267. id: 1,
  268. name: null,
  269. _model: undefined,
  270. })
  271. expect(entityManager.newInstance).toHaveBeenCalledWith(DummyApiResource, {
  272. id: 2,
  273. name: null,
  274. _model: undefined,
  275. })
  276. expect(entityManager.newInstance).toHaveBeenCalledWith(DummyApiResource, {
  277. id: 3,
  278. name: null,
  279. _model: undefined,
  280. })
  281. // @ts-ignore
  282. piniaOrmQuery.get = vi.fn(() => [
  283. new DummyApiResource({ id: 1 }),
  284. new DummyApiResource({ id: 2 }),
  285. new DummyApiResource({ id: 3 }),
  286. ])
  287. expect(result.value.items).toEqual([
  288. new DummyApiResource({ id: 1 }),
  289. new DummyApiResource({ id: 2 }),
  290. new DummyApiResource({ id: 3 }),
  291. ])
  292. expect(result.value.pagination, 'default pagination').toEqual({
  293. first: 1,
  294. last: 1,
  295. next: undefined,
  296. previous: undefined,
  297. })
  298. // @ts-expect-error Needed to avoid 'Cannot stringify non POJO' occasional bugs
  299. expect(result.toJSON()).toEqual(
  300. 'Computed result from fetchCollection at : api/dummyResource',
  301. )
  302. })
  303. test('with a parent', async () => {
  304. const collection = {
  305. '@type': 'Collection',
  306. totalItems: 3,
  307. member: [{ id: 1 }, { id: 2 }, { id: 3 }],
  308. }
  309. // @ts-ignore
  310. apiRequestService.get = vi.fn(async (url: string) => collection)
  311. const piniaOrmQuery = vi.fn()
  312. // @ts-ignore
  313. entityManager.getQuery = vi.fn((model: typeof ApiResource) => piniaOrmQuery)
  314. // @ts-ignore
  315. entityManager.newInstance = vi.fn(
  316. (model: typeof ApiResource, props: object) => {
  317. return new DummyApiResource(props)
  318. },
  319. )
  320. const parent = new DummyApiModel()
  321. parent.id = 100
  322. parent.entity = 'dummyModel' // TODO: je ne comprend pas pqoi cette ligne est nécessaire...
  323. await entityManager.fetchCollection(DummyApiResource, parent)
  324. expect(apiRequestService.get).toHaveBeenCalledWith(
  325. 'api/dummyModel/100/dummyResource',
  326. )
  327. })
  328. test('with a query', async () => {
  329. const collection = {
  330. '@type': 'Collection',
  331. totalItems: 3,
  332. member: [{ id: 1 }, { id: 2 }, { id: 3 }],
  333. }
  334. const query = vi.fn()
  335. // @ts-ignore
  336. query.getUrlQuery = vi.fn(() => 'foo=bar')
  337. // @ts-ignore
  338. query.applyToPiniaOrmQuery = vi.fn((q) => q)
  339. const piniaOrmQuery = vi.fn()
  340. // @ts-ignore
  341. entityManager.getQuery = vi.fn((model: typeof ApiResource) => piniaOrmQuery)
  342. // @ts-ignore
  343. apiRequestService.get = vi.fn(async (url: string) => collection)
  344. // @ts-ignore
  345. entityManager.newInstance = vi.fn(
  346. (model: typeof ApiResource, props: object) => {
  347. return new DummyApiResource(props)
  348. },
  349. )
  350. // @ts-ignore
  351. await entityManager.fetchCollection(DummyApiResource, null, query)
  352. expect(apiRequestService.get).toHaveBeenCalledWith(
  353. 'api/dummyResource?foo=bar',
  354. )
  355. // @ts-ignore
  356. expect(query.getUrlQuery).toHaveBeenCalledWith()
  357. // @ts-ignore
  358. expect(query.applyToPiniaOrmQuery).toHaveBeenCalledWith(piniaOrmQuery)
  359. })
  360. })
  361. describe('persist', () => {
  362. test('new entity (POST)', async () => {
  363. const instance = new DummyApiModel({ id: 'tmp1', name: 'bob' })
  364. instance.isNew = vi.fn(() => true)
  365. // @ts-ignore
  366. instance.$toJson = vi.fn(() => {
  367. return { id: 'tmp1', name: 'bob' }
  368. })
  369. // @ts-ignore
  370. entityManager.cast = vi.fn(
  371. (model: typeof ApiResource, entity: ApiResource): ApiResource => entity,
  372. )
  373. entityManager.validateEntity = vi.fn((instance: unknown) => {})
  374. const response = { id: 1, name: 'bob' }
  375. // @ts-ignore
  376. apiRequestService.post = vi.fn((url, data) => response)
  377. // @ts-ignore
  378. entityManager.newInstance = vi.fn((model, response) => {
  379. const newEntity = new DummyApiModel(response)
  380. // @ts-ignore
  381. newEntity.id = response.id
  382. // @ts-ignore
  383. newEntity.name = response.name
  384. return newEntity
  385. })
  386. // @ts-ignore
  387. entityManager.removeTempAfterPersist = vi.fn()
  388. entityManager.makeProfileHash = vi.fn(async () => await 'azerty')
  389. const result = await entityManager.persist(instance)
  390. // temp id should have been purged from the posted data
  391. expect(apiRequestService.post).toHaveBeenCalledWith(
  392. 'api/dummyModel',
  393. {
  394. name: 'bob',
  395. },
  396. null,
  397. { profileHash: 'azerty' },
  398. )
  399. expect(entityManager.newInstance).toHaveBeenCalledWith(
  400. DummyApiModel,
  401. response,
  402. )
  403. // @ts-ignore
  404. expect(entityManager.removeTempAfterPersist).toHaveBeenCalledWith(
  405. DummyApiModel,
  406. instance.id,
  407. )
  408. // @ts-ignore
  409. expect(entityManager.makeProfileHash).toHaveBeenCalledTimes(1)
  410. expect(result.id).toEqual(1)
  411. expect(result.name).toEqual('bob')
  412. expect(entityManager.validateEntity).toHaveBeenCalledWith(instance)
  413. })
  414. test('existing entity (PUT)', async () => {
  415. const props = { id: 1, name: 'bob' }
  416. const entity = new DummyApiModel(props)
  417. entity.id = 1
  418. entity.isNew = vi.fn(() => false)
  419. // @ts-ignore
  420. entity.$toJson = vi.fn(() => props)
  421. // TODO: attendre de voir si cet appel est nécessaire dans l'entity manager
  422. // entityManager.cast = vi.fn((model: typeof ApiResource, entity: ApiResource): ApiResource => entity)
  423. // @ts-ignore
  424. apiRequestService.patch = vi.fn((url, data) => props)
  425. // @ts-ignore
  426. entityManager.newInstance = vi.fn((model, response) => {
  427. const newEntity = new DummyApiModel(response)
  428. // @ts-ignore
  429. newEntity.id = response.id
  430. // @ts-ignore
  431. newEntity.name = response.name
  432. return newEntity
  433. })
  434. // @ts-ignore
  435. entityManager.removeTempAfterPersist = vi.fn()
  436. entityManager.makeProfileHash = vi.fn(async () => await 'azerty')
  437. entityManager.validateEntity = vi.fn((instance: unknown) => {})
  438. const result = await entityManager.persist(entity)
  439. expect(apiRequestService.patch).toHaveBeenCalledWith(
  440. 'api/dummyModel/1',
  441. {
  442. id: 1,
  443. name: 'bob',
  444. },
  445. null,
  446. { profileHash: 'azerty' },
  447. )
  448. expect(entityManager.newInstance).toHaveBeenCalledWith(DummyApiModel, props)
  449. // @ts-ignore
  450. expect(entityManager.removeTempAfterPersist).toHaveBeenCalledTimes(0)
  451. // @ts-ignore
  452. expect(entityManager.makeProfileHash).toHaveBeenCalledTimes(1)
  453. expect(result.id).toEqual(1)
  454. expect(result.name).toEqual('bob')
  455. expect(entityManager.validateEntity).toHaveBeenCalledWith(entity)
  456. })
  457. })
  458. describe('patch', () => {
  459. test('simple call', async () => {
  460. const props = { id: 1, name: 'bobby' }
  461. // @ts-ignore
  462. apiRequestService.patch = vi.fn((url, data) => props)
  463. // @ts-ignore
  464. entityManager.newInstance = vi.fn((model, response) => {
  465. const newEntity = new DummyApiModel(response)
  466. // @ts-ignore
  467. newEntity.id = response.id
  468. // @ts-ignore
  469. newEntity.name = response.name
  470. return newEntity
  471. })
  472. const result = await entityManager.patch(DummyApiModel, 1, {
  473. name: 'bobby',
  474. })
  475. expect(apiRequestService.patch).toHaveBeenCalledWith(
  476. 'api/dummyModel/1',
  477. '{"name":"bobby"}',
  478. )
  479. expect(entityManager.newInstance).toHaveBeenCalledWith(DummyApiModel, {
  480. id: 1,
  481. name: 'bobby',
  482. })
  483. expect(result.id).toEqual(1)
  484. expect(result.name).toEqual('bobby')
  485. })
  486. })
  487. describe('delete', () => {
  488. test('delete non persisted entity', () => {
  489. const entity = new DummyApiModel()
  490. entity.id = 'tmp123'
  491. // @ts-ignore
  492. const repo = vi.fn() as Repository<ApiResource>
  493. // @ts-ignore
  494. entityManager.getRepository = vi.fn((model: typeof ApiResource) => {
  495. return model === DummyApiModel ? repo : null
  496. })
  497. entityManager.validateEntity = vi.fn((instance: unknown) => {})
  498. apiRequestService.delete = vi.fn()
  499. // @ts-ignore
  500. repo.destroy = vi.fn((id: number) => null)
  501. entityManager.delete(entity)
  502. expect(entityManager.getRepository).toHaveBeenCalledWith(DummyApiModel)
  503. expect(apiRequestService.delete).toHaveBeenCalledTimes(0)
  504. expect(repo.destroy).toHaveBeenCalledWith('tmp123')
  505. expect(entityManager.validateEntity).toHaveBeenCalledWith(entity)
  506. })
  507. test('delete persisted entity', async () => {
  508. const entity = new DummyApiModel()
  509. entity.isNew = vi.fn(() => false)
  510. entity.id = 1
  511. // @ts-ignore
  512. const repo = vi.fn() as Repository<ApiResource>
  513. // @ts-ignore
  514. entityManager.getRepository = vi.fn((model: typeof ApiResource) => {
  515. return model === DummyApiModel ? repo : null
  516. })
  517. // @ts-ignore
  518. apiRequestService.delete = vi.fn((id: number) => null)
  519. // @ts-ignore
  520. repo.destroy = vi.fn((id: number) => null)
  521. await entityManager.delete(entity)
  522. expect(entityManager.getRepository).toHaveBeenCalledWith(DummyApiModel)
  523. expect(apiRequestService.delete).toHaveBeenCalledWith('api/dummyModel/1')
  524. expect(repo.destroy).toHaveBeenCalledWith(1)
  525. })
  526. })
  527. describe('reset', () => {
  528. test('simple call', () => {
  529. const entity = new DummyApiModel()
  530. entity.id = 1
  531. entity.name = 'paul'
  532. const initialEntity = new DummyApiModel()
  533. initialEntity.id = 1
  534. initialEntity.name = 'serge'
  535. // @ts-ignore
  536. entityManager.getInitialStateOf = vi.fn(
  537. (model: typeof ApiResource, id: string | number) => initialEntity,
  538. )
  539. // @ts-ignore
  540. const repo = vi.fn() as Repository<ApiResource>
  541. // @ts-ignore
  542. entityManager.getRepository = vi.fn((model: typeof ApiResource) => {
  543. return model === DummyApiModel ? repo : null
  544. })
  545. // @ts-ignore
  546. repo.save = vi.fn((data: any) => null)
  547. const result = entityManager.reset(initialEntity)
  548. // @ts-ignore
  549. expect(entityManager.getInitialStateOf).toHaveBeenCalledWith(
  550. DummyApiModel,
  551. 1,
  552. )
  553. expect(repo.save).toHaveBeenCalledWith(initialEntity)
  554. expect(result).toEqual(initialEntity)
  555. })
  556. test('no initial state stored', () => {
  557. const entity = new DummyApiModel()
  558. entity.id = 1
  559. // @ts-ignore
  560. entityManager.getInitialStateOf = vi.fn(
  561. (model: typeof ApiResource, id: string | number) => null,
  562. )
  563. // @ts-ignore
  564. entityManager.getModel = vi.fn((instance: ApiResource) => DummyApiModel)
  565. expect(() => entityManager.reset(entity)).toThrowError(
  566. 'no initial state recorded for this object - abort [dummyModel/1]',
  567. )
  568. })
  569. })
  570. describe('flush', () => {
  571. test('simple call', () => {
  572. // @ts-ignore
  573. const repo = vi.fn() as Repository<ApiResource>
  574. // @ts-ignore
  575. entityManager.getRepository = vi.fn((model: typeof ApiResource) => {
  576. return model === DummyApiModel ? repo : null
  577. })
  578. repo.flush = vi.fn()
  579. entityManager.flush(DummyApiModel)
  580. expect(repo.flush).toHaveBeenCalled()
  581. })
  582. })
  583. describe('isNewEntity', () => {
  584. test('with new entity', () => {
  585. const entity = new DummyApiModel()
  586. entity.isNew = vi.fn(() => true)
  587. // @ts-ignore
  588. const repo = vi.fn() as Repository<ApiResource>
  589. // @ts-ignore
  590. entityManager.getRepository = vi.fn((model: typeof ApiResource) => {
  591. return model === DummyApiModel ? repo : null
  592. })
  593. // @ts-ignore
  594. repo.find = vi.fn((id: number) => entity)
  595. const result = entityManager.isNewInstance(DummyApiModel, 1)
  596. expect(result).toBeTruthy()
  597. })
  598. test('with existing entity', () => {
  599. const entity = new DummyApiModel()
  600. entity.isNew = vi.fn(() => false)
  601. // @ts-ignore
  602. const repo = vi.fn() as Repository<ApiResource>
  603. // @ts-ignore
  604. entityManager.getRepository = vi.fn((model: typeof ApiResource) => {
  605. return model === DummyApiModel ? repo : null
  606. })
  607. // @ts-ignore
  608. repo.find = vi.fn((id: number) => entity)
  609. const result = entityManager.isNewInstance(DummyApiModel, 1)
  610. expect(result).toBeFalsy()
  611. })
  612. test('non-existing entity', () => {
  613. // @ts-ignore
  614. const repo = vi.fn() as Repository<ApiResource>
  615. // @ts-ignore
  616. entityManager.getRepository = vi.fn((model: typeof ApiResource) => {
  617. return model === DummyApiModel ? repo : null
  618. })
  619. // @ts-ignore
  620. repo.find = vi.fn((id: number) => null)
  621. console.error = vi.fn()
  622. const result = entityManager.isNewInstance(DummyApiModel, 1)
  623. expect(result).toBeFalsy()
  624. expect(console.error).toHaveBeenCalledWith('dummyModel/1 does not exist!')
  625. })
  626. })
  627. describe('saveInitialState', () => {
  628. test('simple call', () => {
  629. // @ts-ignore
  630. const entity = { id: 1, name: 'bob' } as DummyApiResource
  631. // @ts-ignore
  632. const repo = vi.fn() as Repository<ApiResource>
  633. // @ts-ignore
  634. entityManager.getRepository = vi.fn((model: typeof ApiResource) => {
  635. return model === DummyApiResource ? repo : null
  636. })
  637. // @ts-ignore
  638. repo.save = vi.fn((record: Element) => null)
  639. // @ts-ignore
  640. entityManager.saveInitialState(DummyApiResource, entity)
  641. expect(repo.save).toHaveBeenCalledWith({ id: '_clone_1', name: 'bob' })
  642. expect(entity.id).toEqual(1)
  643. })
  644. })
  645. describe('getInitialStateOf', () => {
  646. test('with initial state', () => {
  647. // @ts-ignore
  648. const entity = { id: 1, name: 'bob' } as DummyApiResource
  649. // @ts-ignore
  650. const repo = vi.fn() as Repository<ApiResource>
  651. // @ts-ignore
  652. entityManager.getRepository = vi.fn((model: typeof ApiResource) => {
  653. return model === DummyApiResource ? repo : null
  654. })
  655. // @ts-ignore
  656. repo.find = vi.fn((id: number | string) => {
  657. // @ts-ignore
  658. return { id: 1, name: 'robert' } as DummyApiResource
  659. })
  660. // @ts-ignore
  661. const result = entityManager.getInitialStateOf(
  662. DummyApiResource,
  663. 1,
  664. ) as DummyApiResource
  665. expect(repo.find).toHaveBeenCalledWith('_clone_1')
  666. expect(result.id).toEqual(1)
  667. expect(result.name).toEqual('robert')
  668. })
  669. test('without initial state', () => {
  670. // @ts-ignore
  671. const repo = vi.fn() as Repository<ApiResource>
  672. // @ts-ignore
  673. entityManager.getRepository = vi.fn((model: typeof ApiResource) => {
  674. return model === DummyApiResource ? repo : null
  675. })
  676. // @ts-ignore
  677. repo.find = vi.fn((id: number | string) => null)
  678. // @ts-ignore
  679. const result = entityManager.getInitialStateOf(
  680. DummyApiResource,
  681. 1,
  682. ) as DummyApiResource
  683. expect(repo.find).toHaveBeenCalledWith('_clone_1')
  684. expect(result).toEqual(null)
  685. })
  686. })
  687. describe('removeTempAfterPersist', () => {
  688. test('simple call', () => {
  689. // @ts-ignore
  690. const entity = new DummyApiResource()
  691. entity.id = 'tmp123'
  692. entity.isNew = vi.fn(() => true)
  693. // @ts-ignore
  694. const repo = vi.fn() as Repository<ApiResource>
  695. // @ts-ignore
  696. entityManager.getRepository = vi.fn((model: typeof ApiResource) => {
  697. return model === DummyApiResource ? repo : null
  698. })
  699. // @ts-ignore
  700. repo.find = vi.fn((id: number | string) => entity)
  701. // @ts-ignore
  702. repo.destroy = vi.fn()
  703. // @ts-ignore
  704. entityManager.removeTempAfterPersist(DummyApiResource, 'tmp123')
  705. expect(repo.destroy).toHaveBeenCalledWith('tmp123')
  706. expect(repo.destroy).toHaveBeenCalledWith('_clone_tmp123')
  707. })
  708. test('entity is not temporary', () => {
  709. // @ts-ignore
  710. const entity = new DummyApiResource()
  711. entity.id = 1
  712. entity.isNew = vi.fn(() => false)
  713. // @ts-ignore
  714. const repo = vi.fn() as Repository<ApiResource>
  715. // @ts-ignore
  716. entityManager.getRepository = vi.fn((model: typeof ApiResource) => {
  717. return model === DummyApiResource ? repo : null
  718. })
  719. // @ts-ignore
  720. repo.find = vi.fn((id: number | string) => entity)
  721. // @ts-ignore
  722. repo.destroy = vi.fn()
  723. // @ts-ignore
  724. expect(() =>
  725. entityManager.removeTempAfterPersist(DummyApiResource, 'tmp123'),
  726. ).toThrowError('Error: Can not remove a non-temporary model instance')
  727. expect(repo.destroy).toHaveBeenCalledTimes(0)
  728. })
  729. test('entity does not exist', () => {
  730. // @ts-ignore
  731. const repo = vi.fn() as Repository<ApiResource>
  732. // @ts-ignore
  733. entityManager.getRepository = vi.fn((model: typeof ApiResource) => {
  734. return model === DummyApiResource ? repo : null
  735. })
  736. // @ts-ignore
  737. repo.find = vi.fn((id: number | string) => null)
  738. // @ts-ignore
  739. repo.destroy = vi.fn()
  740. console.error = vi.fn()
  741. // @ts-ignore
  742. entityManager.removeTempAfterPersist(DummyApiResource, 'tmp123')
  743. expect(repo.destroy).toHaveBeenCalledTimes(0)
  744. expect(console.error).toHaveBeenCalledWith(
  745. 'dummyResource/tmp123 does not exist!',
  746. )
  747. })
  748. })
  749. describe('makeProfileHash', () => {
  750. test('simple call', async () => {
  751. entityManager._getProfileMask = vi.fn(() => {
  752. return { a: 1 }
  753. })
  754. expect(await entityManager.makeProfileHash()).toEqual(
  755. '9f89c740ceb46d7418c924a78ac57941d5e96520',
  756. )
  757. })
  758. })
  759. describe('validateEntity', () => {
  760. test('instance with numeric id', async () => {
  761. entityManager.validateEntity({ id: 123 })
  762. })
  763. test('instance with temp id', async () => {
  764. entityManager.validateEntity({ id: 'tmpazerty' })
  765. })
  766. test('invalid entity', async () => {
  767. expect(() => entityManager.validateEntity({ id: 'azerty' })).toThrowError(
  768. 'Definition error for the entity, did you use the entityManager.newInstance(...) method?\n{"id":"azerty"}',
  769. )
  770. })
  771. })