id-codec.spec.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. import { beforeEach, describe, expect, it } from 'vitest';
  2. import { DECODED, ENCODED, MockIdStrategy } from '../../config/config.service.mock';
  3. import { IdCodec } from './id-codec';
  4. describe('IdCodecService', () => {
  5. let idCodec: IdCodec;
  6. beforeEach(async () => {
  7. idCodec = new IdCodec(new MockIdStrategy());
  8. });
  9. describe('encode()', () => {
  10. it('works with a string', () => {
  11. const input = 'id';
  12. const result = idCodec.encode(input);
  13. expect(result).toEqual(ENCODED);
  14. });
  15. it('works with a number', () => {
  16. const input = 123;
  17. const result = idCodec.encode(input);
  18. expect(result).toEqual(ENCODED);
  19. });
  20. it('works with a boolean', () => {
  21. const input = true;
  22. const result = idCodec.encode(input);
  23. expect(result).toEqual(true);
  24. });
  25. it('passes through null or undefined without throwing', () => {
  26. expect(idCodec.encode(null as any)).toBeNull();
  27. expect(idCodec.encode(undefined as any)).toBeUndefined();
  28. });
  29. it('returns a clone of the input object', () => {
  30. const input = { id: 'id', name: 'foo' };
  31. const result = idCodec.encode(input);
  32. expect(result).not.toBe(input);
  33. });
  34. it('returns a deep clone', () => {
  35. const obj1 = { 1: true };
  36. const obj2 = { 2: true };
  37. const arr = [obj1, obj2];
  38. const parent = { myArray: arr };
  39. const input = { foo: parent };
  40. const result = idCodec.encode(input);
  41. expect(result).not.toBe(input);
  42. expect(result.foo).not.toBe(parent);
  43. expect(result.foo.myArray).not.toBe(arr);
  44. expect(result.foo.myArray[0]).not.toBe(obj1);
  45. expect(result.foo.myArray[1]).not.toBe(obj2);
  46. });
  47. it('does not clone complex object instances', () => {
  48. /* eslint-disable @typescript-eslint/no-floating-promises */
  49. const promise = new Promise(() => {
  50. /**/
  51. });
  52. const date = new Date();
  53. const regex = new RegExp('');
  54. const input = {
  55. promise,
  56. date,
  57. regex,
  58. };
  59. const result = idCodec.encode(input);
  60. expect(result.promise).toBe(promise);
  61. expect(result.date).toBe(date);
  62. expect(result.regex).toBe(regex);
  63. /* eslint-enable @typescript-eslint/no-floating-promises */
  64. });
  65. it('works with simple entity', () => {
  66. const input = { id: 'id', name: 'foo' };
  67. const result = idCodec.encode(input);
  68. expect(result).toEqual({ id: ENCODED, name: 'foo' });
  69. });
  70. it('works with 2-level nested entities', () => {
  71. const input = {
  72. id: 'id',
  73. friend: { id: 'id' },
  74. };
  75. const result = idCodec.encode(input);
  76. expect(result).toEqual({
  77. id: ENCODED,
  78. friend: { id: ENCODED },
  79. });
  80. });
  81. it('works with 3-level nested entities', () => {
  82. const input = {
  83. id: 'id',
  84. friend: {
  85. dog: { id: 'id' },
  86. },
  87. };
  88. const result = idCodec.encode(input);
  89. expect(result).toEqual({
  90. id: ENCODED,
  91. friend: {
  92. dog: { id: ENCODED },
  93. },
  94. });
  95. });
  96. it('works with list of simple entities', () => {
  97. const input = [
  98. { id: 'id', name: 'foo' },
  99. { id: 'id', name: 'bar' },
  100. ];
  101. const result = idCodec.encode(input);
  102. expect(result).toEqual([
  103. { id: ENCODED, name: 'foo' },
  104. { id: ENCODED, name: 'bar' },
  105. ]);
  106. });
  107. it('does not throw with an empty list', () => {
  108. const input: any[] = [];
  109. const result = idCodec.encode(input);
  110. expect(() => idCodec.encode(input)).not.toThrow();
  111. });
  112. it('works with nested list of simple entities', () => {
  113. const input = {
  114. items: [
  115. { id: 'id', name: 'foo' },
  116. { id: 'id', name: 'bar' },
  117. ],
  118. };
  119. const result = idCodec.encode(input);
  120. expect(result).toEqual({
  121. items: [
  122. { id: ENCODED, name: 'foo' },
  123. { id: ENCODED, name: 'bar' },
  124. ],
  125. });
  126. });
  127. it('works with large and nested list', () => {
  128. const length = 100;
  129. const input = {
  130. items: Array.from({ length }).map(() => ({
  131. id: 'id',
  132. name: { bar: 'baz' },
  133. foo: 'yo',
  134. friends: [{ id: 'id', name: { first: 'boris', id: 'id' } }],
  135. })),
  136. };
  137. const result = idCodec.encode(input);
  138. expect(result.items.length).toBe(length);
  139. expect(result.items[0].id).toBe(ENCODED);
  140. expect(result.items[0].friends[0].id).toBe(ENCODED);
  141. expect(result.items[0].friends[0].name.id).toBe(ENCODED);
  142. });
  143. it('works with lists with a nullable object property', () => {
  144. const input = {
  145. items: [{ user: null }, { user: { id: 'id' } }],
  146. };
  147. const result = idCodec.encode(input);
  148. expect(result.items[0].user).toBe(null);
  149. expect(result.items[1].user).toEqual({ id: ENCODED });
  150. });
  151. it('works with nested list of nested lists', () => {
  152. const input = {
  153. items: [
  154. {
  155. id: 'id',
  156. friends: [{ id: 'id' }, { id: 'id' }],
  157. },
  158. {
  159. id: 'id',
  160. friends: [{ id: 'id' }, { id: 'id' }],
  161. },
  162. ],
  163. };
  164. const result = idCodec.encode(input);
  165. expect(result).toEqual({
  166. items: [
  167. {
  168. id: ENCODED,
  169. friends: [{ id: ENCODED }, { id: ENCODED }],
  170. },
  171. {
  172. id: ENCODED,
  173. friends: [{ id: ENCODED }, { id: ENCODED }],
  174. },
  175. ],
  176. });
  177. });
  178. it('transformKeys can be customized', () => {
  179. const input = { id: 'id', name: 'foo' };
  180. const result = idCodec.encode(input, ['name']);
  181. expect(result).toEqual({ id: ENCODED, name: ENCODED });
  182. });
  183. });
  184. describe('decode()', () => {
  185. it('works with a string', () => {
  186. const input = 'id';
  187. const result = idCodec.decode(input);
  188. expect(result).toEqual(DECODED);
  189. });
  190. it('works with a number', () => {
  191. const input = 123;
  192. const result = idCodec.decode(input);
  193. expect(result).toEqual(DECODED);
  194. });
  195. it('works with simple entity', () => {
  196. const input = { id: 'id', name: 'foo' };
  197. const result = idCodec.decode(input);
  198. expect(result).toEqual({ id: DECODED, name: 'foo' });
  199. });
  200. it('works with 2-level nested entities', () => {
  201. const input = {
  202. id: 'id',
  203. friend: { id: 'id' },
  204. };
  205. const result = idCodec.decode(input);
  206. expect(result).toEqual({
  207. id: DECODED,
  208. friend: { id: DECODED },
  209. });
  210. });
  211. it('works with 3-level nested entities', () => {
  212. const input = {
  213. id: 'id',
  214. friend: {
  215. dog: { id: 'id' },
  216. },
  217. };
  218. const result = idCodec.decode(input);
  219. expect(result).toEqual({
  220. id: DECODED,
  221. friend: {
  222. dog: { id: DECODED },
  223. },
  224. });
  225. });
  226. it('works with list of simple entities', () => {
  227. const input = [
  228. { id: 'id', name: 'foo' },
  229. { id: 'id', name: 'bar' },
  230. ];
  231. const result = idCodec.decode(input);
  232. expect(result).toEqual([
  233. { id: DECODED, name: 'foo' },
  234. { id: DECODED, name: 'bar' },
  235. ]);
  236. });
  237. it('works with nested list of simple entities', () => {
  238. const input = {
  239. items: [
  240. { id: 'id', name: 'foo' },
  241. { id: 'id', name: 'bar' },
  242. ],
  243. };
  244. const result = idCodec.decode(input);
  245. expect(result).toEqual({
  246. items: [
  247. { id: DECODED, name: 'foo' },
  248. { id: DECODED, name: 'bar' },
  249. ],
  250. });
  251. });
  252. it('works with lists with a nullable object property', () => {
  253. const input = {
  254. items: [{ user: null }, { user: { id: 'id' } }],
  255. };
  256. const result = idCodec.decode(input);
  257. expect(result.items[0].user).toBe(null);
  258. expect(result.items[1].user).toEqual({ id: DECODED });
  259. });
  260. it('works with large and nested list', () => {
  261. const length = 100;
  262. const input = {
  263. items: Array.from({ length }).map(() => ({
  264. id: 'id',
  265. name: { bar: 'baz' },
  266. foo: 'yo',
  267. friends: [{ id: 'id', name: { first: 'boris', id: 'id' } }],
  268. })),
  269. };
  270. const result = idCodec.decode(input);
  271. expect(result.items.length).toBe(length);
  272. expect(result.items[0].id).toBe(DECODED);
  273. expect(result.items[0].friends[0].id).toBe(DECODED);
  274. expect(result.items[0].friends[0].name.id).toBe(DECODED);
  275. });
  276. it('works with nested list of nested lists', () => {
  277. const input = {
  278. items: [
  279. {
  280. id: 'id',
  281. friends: [{ id: 'id' }, { id: 'id' }],
  282. },
  283. {
  284. id: 'id',
  285. friends: [{ id: 'id' }, { id: 'id' }],
  286. },
  287. ],
  288. };
  289. const result = idCodec.decode(input);
  290. expect(result).toEqual({
  291. items: [
  292. {
  293. id: DECODED,
  294. friends: [{ id: DECODED }, { id: DECODED }],
  295. },
  296. {
  297. id: DECODED,
  298. friends: [{ id: DECODED }, { id: DECODED }],
  299. },
  300. ],
  301. });
  302. });
  303. it('transformKeys can be customized', () => {
  304. const input = { name: 'foo' };
  305. const result = idCodec.decode(input, ['name']);
  306. expect(result).toEqual({ name: DECODED });
  307. });
  308. it('id keys is still implicitly decoded when transformKeys are defined', () => {
  309. const input = { id: 'id', name: 'foo' };
  310. const result = idCodec.decode(input, ['name']);
  311. expect(result).toEqual({ id: DECODED, name: DECODED });
  312. });
  313. it('transformKeys works for nested matching keys', () => {
  314. const input = {
  315. input: {
  316. id: 'id',
  317. featuredAssetId: 'id',
  318. foo: 'bar',
  319. },
  320. };
  321. const result = idCodec.decode(input, ['featuredAssetId']);
  322. expect(result).toEqual({
  323. input: {
  324. id: DECODED,
  325. featuredAssetId: DECODED,
  326. foo: 'bar',
  327. },
  328. });
  329. });
  330. it('transformKeys works for nested matching key array', () => {
  331. const input = {
  332. input: {
  333. id: 'id',
  334. assetIds: ['id1', 'id2', 'id3'],
  335. foo: 'bar',
  336. },
  337. };
  338. const result = idCodec.decode(input, ['assetIds']);
  339. expect(result).toEqual({
  340. input: {
  341. id: DECODED,
  342. assetIds: [DECODED, DECODED, DECODED],
  343. foo: 'bar',
  344. },
  345. });
  346. });
  347. it('transformKeys works for multiple nested keys', () => {
  348. const input = {
  349. input: {
  350. id: 'id',
  351. featuredAssetId: 'id',
  352. assetIds: ['id1', 'id2', 'id3'],
  353. foo: 'bar',
  354. },
  355. };
  356. const result = idCodec.decode(input, ['featuredAssetId', 'assetIds']);
  357. expect(result).toEqual({
  358. input: {
  359. id: DECODED,
  360. featuredAssetId: DECODED,
  361. assetIds: [DECODED, DECODED, DECODED],
  362. foo: 'bar',
  363. },
  364. });
  365. });
  366. // https://github.com/vendure-ecommerce/vendure/issues/1596
  367. it('works with heterogeneous array', () => {
  368. const input1 = { value: [null, 'foo'] };
  369. const input2 = { value: [false, 'foo'] };
  370. const input3 = { value: [{}, 'foo'] };
  371. const input4 = { value: [[], 'foo'] };
  372. const input5 = { value: [0, 'foo'] };
  373. const result1 = idCodec.decode(input1);
  374. const result2 = idCodec.decode(input2);
  375. const result3 = idCodec.decode(input3);
  376. const result4 = idCodec.decode(input4);
  377. const result5 = idCodec.decode(input5);
  378. expect(result1).toEqual(input1);
  379. expect(result2).toEqual(input2);
  380. expect(result3).toEqual(input3);
  381. expect(result4).toEqual(input4);
  382. expect(result5).toEqual(input5);
  383. });
  384. });
  385. });