entityManager.test.ts 26 KB

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