@aidc-toolkit/gs1 1.0.31-beta → 1.0.33-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +290 -17
  2. package/dist/character-set.d.ts +17 -2
  3. package/dist/character-set.d.ts.map +1 -1
  4. package/dist/character-set.js +44 -11
  5. package/dist/character-set.js.map +1 -1
  6. package/dist/gcp-length-cache.d.ts +81 -0
  7. package/dist/gcp-length-cache.d.ts.map +1 -0
  8. package/dist/gcp-length-cache.js +232 -0
  9. package/dist/gcp-length-cache.js.map +1 -0
  10. package/dist/gcp-length-data.d.ts +108 -0
  11. package/dist/gcp-length-data.d.ts.map +1 -0
  12. package/dist/gcp-length-data.js +53 -0
  13. package/dist/gcp-length-data.js.map +1 -0
  14. package/dist/gcp-length.d.ts +61 -0
  15. package/dist/gcp-length.d.ts.map +1 -0
  16. package/dist/gcp-length.js +300 -0
  17. package/dist/gcp-length.js.map +1 -0
  18. package/dist/gtin-creator.d.ts +0 -17
  19. package/dist/gtin-creator.d.ts.map +1 -1
  20. package/dist/gtin-creator.js +1 -93
  21. package/dist/gtin-creator.js.map +1 -1
  22. package/dist/gtin-validator.d.ts +1 -46
  23. package/dist/gtin-validator.d.ts.map +1 -1
  24. package/dist/gtin-validator.js +31 -125
  25. package/dist/gtin-validator.js.map +1 -1
  26. package/dist/identifier-validator.d.ts +1 -4
  27. package/dist/identifier-validator.d.ts.map +1 -1
  28. package/dist/identifier-validator.js +2 -5
  29. package/dist/identifier-validator.js.map +1 -1
  30. package/dist/index.d.ts +5 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +4 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/locale/en/locale-resources.d.ts +39 -35
  35. package/dist/locale/en/locale-resources.d.ts.map +1 -1
  36. package/dist/locale/en/locale-resources.js +6 -2
  37. package/dist/locale/en/locale-resources.js.map +1 -1
  38. package/dist/locale/fr/locale-resources.d.ts +39 -35
  39. package/dist/locale/fr/locale-resources.d.ts.map +1 -1
  40. package/dist/locale/fr/locale-resources.js +6 -2
  41. package/dist/locale/fr/locale-resources.js.map +1 -1
  42. package/dist/locale/i18n.d.ts +4 -4
  43. package/dist/locale/i18n.d.ts.map +1 -1
  44. package/dist/locale/i18n.js +6 -7
  45. package/dist/locale/i18n.js.map +1 -1
  46. package/dist/non-numeric-identifier-validator.js +1 -1
  47. package/dist/non-numeric-identifier-validator.js.map +1 -1
  48. package/dist/numeric-identifier-validator.d.ts.map +1 -1
  49. package/dist/numeric-identifier-validator.js +2 -2
  50. package/dist/numeric-identifier-validator.js.map +1 -1
  51. package/dist/prefix-manager.d.ts +35 -0
  52. package/dist/prefix-manager.d.ts.map +1 -1
  53. package/dist/prefix-manager.js +56 -0
  54. package/dist/prefix-manager.js.map +1 -1
  55. package/dist/prefix-validator.d.ts +5 -4
  56. package/dist/prefix-validator.d.ts.map +1 -1
  57. package/dist/prefix-validator.js +18 -22
  58. package/dist/prefix-validator.js.map +1 -1
  59. package/dist/serializable-numeric-identifier-validator.d.ts +26 -0
  60. package/dist/serializable-numeric-identifier-validator.d.ts.map +1 -1
  61. package/dist/serializable-numeric-identifier-validator.js +19 -0
  62. package/dist/serializable-numeric-identifier-validator.js.map +1 -1
  63. package/dist/variable-measure.d.ts +68 -0
  64. package/dist/variable-measure.d.ts.map +1 -0
  65. package/dist/variable-measure.js +210 -0
  66. package/dist/variable-measure.js.map +1 -0
  67. package/dist/verified-by-gs1.d.ts +22 -0
  68. package/dist/verified-by-gs1.d.ts.map +1 -0
  69. package/dist/verified-by-gs1.js +46 -0
  70. package/dist/verified-by-gs1.js.map +1 -0
  71. package/package.json +12 -11
  72. package/src/character-set.ts +55 -11
  73. package/src/gcp-length-cache.ts +271 -0
  74. package/src/gcp-length-data.ts +136 -0
  75. package/src/gcp-length.ts +380 -0
  76. package/src/gtin-creator.ts +1 -117
  77. package/src/gtin-validator.ts +42 -173
  78. package/src/identifier-validator.ts +2 -5
  79. package/src/index.ts +7 -1
  80. package/src/locale/en/locale-resources.ts +7 -3
  81. package/src/locale/fr/locale-resources.ts +7 -3
  82. package/src/locale/i18n.ts +7 -8
  83. package/src/locale/i18next.d.ts +2 -0
  84. package/src/non-numeric-identifier-validator.ts +1 -1
  85. package/src/numeric-identifier-validator.ts +3 -3
  86. package/src/prefix-manager.ts +65 -0
  87. package/src/prefix-validator.ts +19 -23
  88. package/src/serializable-numeric-identifier-validator.ts +36 -0
  89. package/src/variable-measure.ts +268 -0
  90. package/src/verified-by-gs1.ts +54 -0
  91. package/test/character-set.test.ts +46 -0
  92. package/test/creator.test.ts +5 -5
  93. package/test/data/gcpprefixformatlist-1.json +662625 -0
  94. package/test/data/gcpprefixformatlist-2.json +735431 -0
  95. package/test/gcp-length.test.ts +344 -0
  96. package/test/gtin-creator.ts +4 -4
  97. package/test/gtin-validator.test.ts +205 -113
  98. package/test/gtin-validator.ts +30 -0
  99. package/test/identifier-creator.ts +6 -6
  100. package/test/non-numeric-identifier-creator.ts +0 -8
  101. package/test/non-serializable-numeric-identifier-creator.ts +4 -54
  102. package/test/numeric-identifier-creator.ts +4 -4
  103. package/test/prefix-manager.test.ts +5 -5
  104. package/test/serializable-numeric-identifier-creator.ts +32 -19
  105. package/test/validator.test.ts +6 -6
  106. package/test/variable-measure-rcn.test.ts +63 -68
  107. package/test/verified-by-gs1.test.ts +55 -0
  108. package/tsconfig-src.json +7 -1
  109. package/tsconfig-src.tsbuildinfo +1 -0
  110. package/tsconfig-tsup.json +7 -0
@@ -0,0 +1,344 @@
1
+ import { type AppDataStorage, LocalAppDataStorage } from "@aidc-toolkit/core";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { afterAll, beforeAll, describe, expect, test } from "vitest";
5
+ import * as GCPLength from "../src/gcp-length.js";
6
+ import {
7
+ GCPLengthCache,
8
+ type GCPLengthData,
9
+ type GCPLengthHeader,
10
+ type GCPLengthSourceJSON,
11
+ IdentifierTypes,
12
+ isGCPLengthHeader,
13
+ isGCPLengthSourceJSON,
14
+ PrefixManager,
15
+ PrefixTypes,
16
+ PrefixValidator,
17
+ RemoteGCPLengthCache
18
+ } from "../src/index.js";
19
+
20
+ const DATA_DIRECTORY = "test/data";
21
+
22
+ class GCPLengthCacheJSONSource extends GCPLengthCache {
23
+ readonly #pathKey: string;
24
+
25
+ #gcpLengthSourceJSON!: GCPLengthSourceJSON;
26
+
27
+ constructor(appDataStorage: AppDataStorage<boolean>, index: number) {
28
+ super(appDataStorage);
29
+
30
+ this.#pathKey = `gcpprefixformatlist-${index}`;
31
+ }
32
+
33
+ get sourceDateTime(): Promise<Date> {
34
+ return this.appDataStorage.read(this.#pathKey).then((appData) => {
35
+ if (!isGCPLengthSourceJSON(appData)) {
36
+ throw new Error(`File ${this.#pathKey}.json has invalid content`);
37
+ }
38
+
39
+ this.#gcpLengthSourceJSON = appData;
40
+
41
+ return new Date(appData.GCPPrefixFormatList.date);
42
+ });
43
+ }
44
+
45
+ get sourceData(): GCPLengthSourceJSON {
46
+ return this.#gcpLengthSourceJSON;
47
+ }
48
+ }
49
+
50
+ class GCPLengthCacheBinarySource extends GCPLengthCache {
51
+ readonly #headerPathKey: string;
52
+
53
+ readonly #dataPathKey: string;
54
+
55
+ #gcpLengthHeader!: GCPLengthHeader;
56
+
57
+ constructor(appDataStorage: AppDataStorage<boolean>, index: number) {
58
+ super(appDataStorage);
59
+
60
+ this.#headerPathKey = `${GCPLengthCache.HEADER_STORAGE_KEY}-${index}`;
61
+ this.#dataPathKey = `${GCPLengthCache.DATA_STORAGE_KEY}-${index}`;
62
+ }
63
+
64
+ get sourceDateTime(): Promise<Date> {
65
+ return this.appDataStorage.read(this.#headerPathKey).then((appData) => {
66
+ if (!isGCPLengthHeader(appData)) {
67
+ throw new Error(`File ${this.#headerPathKey}.json not found or has invalid content`);
68
+ }
69
+
70
+ this.#gcpLengthHeader = appData;
71
+
72
+ return appData.dateTime;
73
+ });
74
+ }
75
+
76
+ get sourceData(): Promise<GCPLengthData> {
77
+ return this.appDataStorage.read(this.#dataPathKey, true).then((appData) => {
78
+ if (!(appData instanceof Uint8Array)) {
79
+ throw new Error(`File ${this.#dataPathKey}.bin not found or has invalid content`);
80
+ }
81
+
82
+ return {
83
+ ...this.#gcpLengthHeader,
84
+ data: appData
85
+ };
86
+ });
87
+ }
88
+ }
89
+
90
+ const NEXT_CHECK_DATE_TIME_PATH = path.resolve(DATA_DIRECTORY, "gcp-length-next-check-date-time.json");
91
+
92
+ const BINARY_HEADER_PATH = path.resolve(DATA_DIRECTORY, "gcp-length-header.json");
93
+
94
+ const BINARY_DATA_PATH = path.resolve(DATA_DIRECTORY, "gcp-length-data.bin");
95
+
96
+ const BINARY_1_HEADER_PATH = path.resolve(DATA_DIRECTORY, "gcp-length-header-1.json");
97
+
98
+ const BINARY_1_DATA_PATH = path.resolve(DATA_DIRECTORY, "gcp-length-data-1.bin");
99
+
100
+ const BINARY_2_HEADER_PATH = path.resolve(DATA_DIRECTORY, "gcp-length-header-2.json");
101
+
102
+ const BINARY_2_DATA_PATH = path.resolve(DATA_DIRECTORY, "gcp-length-data-2.bin");
103
+
104
+ describe("GS1 Company Prefix length", () => {
105
+ function verifyEqual(prefix: string, node1: GCPLength.Node | undefined, node2: GCPLength.Node | undefined): void {
106
+ if (node1 !== undefined) {
107
+ if (node2 === undefined) {
108
+ throw new RangeError(`Prefix ${prefix}: node1 defined, node2 undefined`);
109
+ }
110
+
111
+ if (GCPLength.isLeaf(node1)) {
112
+ if (!GCPLength.isLeaf(node2)) {
113
+ throw new RangeError(`Prefix ${prefix}: node1 is leaf, node2 is branch`);
114
+ }
115
+
116
+ if (node1.length !== node2.length) {
117
+ throw new RangeError(`Prefix ${prefix}: node1.length = ${node1.length}, node2.length = ${node2.length}`);
118
+ }
119
+ } else if (!GCPLength.isLeaf(node2)) {
120
+ for (let index = 0; index < 10; index++) {
121
+ verifyEqual(`${prefix}${index}`, node1.childNodes[index], (node2 as GCPLength.Branch).childNodes[index]);
122
+ }
123
+ } else {
124
+ throw new RangeError(`Prefix ${prefix}: node1 is branch, node2 is leaf`);
125
+ }
126
+ } else if (node2 !== undefined) {
127
+ throw new RangeError(`Prefix ${prefix}: node1 undefined, node2 defined`);
128
+ }
129
+ }
130
+
131
+ function rm(...paths: string[]): void {
132
+ for (const path of paths) {
133
+ fs.rmSync(path, {
134
+ force: true
135
+ });
136
+ }
137
+ }
138
+
139
+ function rmCache(): void {
140
+ rm(NEXT_CHECK_DATE_TIME_PATH,
141
+ BINARY_HEADER_PATH, BINARY_DATA_PATH,
142
+ BINARY_1_HEADER_PATH, BINARY_1_DATA_PATH,
143
+ BINARY_2_HEADER_PATH, BINARY_2_DATA_PATH
144
+ );
145
+ }
146
+
147
+ function save(fileName: string, index: number): void {
148
+ fs.renameSync(fileName, fileName.replace(".", `-${index}.`));
149
+ }
150
+
151
+ function saveBinary(index: number): void {
152
+ save(BINARY_HEADER_PATH, index);
153
+ save(BINARY_DATA_PATH, index);
154
+ }
155
+
156
+ function restore(fileName: string, index: number): void {
157
+ fs.renameSync(fileName.replace(".", `-${index}.`), fileName);
158
+ }
159
+
160
+ function restoreBinary(index: number): void {
161
+ restore(BINARY_HEADER_PATH, index);
162
+ restore(BINARY_DATA_PATH, index);
163
+ }
164
+
165
+ beforeAll(rmCache);
166
+ afterAll(rmCache);
167
+
168
+ test("Load", async () => {
169
+ const appDataStorage = new (await LocalAppDataStorage)(DATA_DIRECTORY);
170
+
171
+ const gcpLengthCacheJSON1Source = new GCPLengthCacheJSONSource(appDataStorage, 1);
172
+ const gcpLengthCacheJSON2Source = new GCPLengthCacheJSONSource(appDataStorage, 2);
173
+ const gcpLengthCacheBinary1Source = new GCPLengthCacheBinarySource(appDataStorage, 1);
174
+ const gcpLengthCacheBinary2Source = new GCPLengthCacheBinarySource(appDataStorage, 2);
175
+
176
+ let root1: GCPLength.Root | undefined;
177
+ let root2: GCPLength.Root | undefined;
178
+ let nextCheckDateTime: Date | undefined;
179
+
180
+ // Binary not available, JSON 1 available.
181
+ await expect(GCPLength.loadData(gcpLengthCacheJSON1Source).then((root) => {
182
+ root1 = root;
183
+ })).resolves.not.toThrowError(RangeError);
184
+
185
+ nextCheckDateTime = await gcpLengthCacheJSON1Source.nextCheckDateTime;
186
+
187
+ expect(root1).not.toBeUndefined();
188
+ expect(nextCheckDateTime).not.toBeUndefined();
189
+
190
+ // Check next check date/time is a day from now.
191
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Known to be defined.
192
+ expect(new Date().getTime() + 24 * 60 * 60 * 1000 - nextCheckDateTime!.getTime()).toBeLessThan(10000);
193
+
194
+ // Binary available, binary 1 not available but not in next check date/time window.
195
+ await expect(GCPLength.loadData(gcpLengthCacheBinary1Source).then((root) => {
196
+ root2 = root;
197
+ })).resolves.not.toThrowError(RangeError);
198
+
199
+ expect(root2).not.toBeUndefined();
200
+
201
+ // Load from JSON 1 equal to load from binary.
202
+ expect(() => {
203
+ verifyEqual("", root1, root2);
204
+ }).not.toThrow(RangeError);
205
+
206
+ saveBinary(1);
207
+ rm(NEXT_CHECK_DATE_TIME_PATH);
208
+
209
+ // Binary not available, JSON 2 available.
210
+ await expect(GCPLength.loadData(gcpLengthCacheJSON2Source).then((root) => {
211
+ root2 = root;
212
+ })).resolves.not.toThrowError(RangeError);
213
+
214
+ nextCheckDateTime = await gcpLengthCacheJSON2Source.nextCheckDateTime;
215
+
216
+ expect(root2).not.toBeUndefined();
217
+ expect(nextCheckDateTime).not.toBeUndefined();
218
+
219
+ saveBinary(2);
220
+ restoreBinary(1);
221
+
222
+ // Binary available, binary 2 available and more recent but not in next check date/time window.
223
+ await expect(GCPLength.loadData(gcpLengthCacheBinary2Source).then((root) => {
224
+ root2 = root;
225
+ })).resolves.not.toThrowError(RangeError);
226
+
227
+ // No change.
228
+ expect(() => {
229
+ verifyEqual("", root1, root2);
230
+ }).not.toThrow(RangeError);
231
+
232
+ await gcpLengthCacheBinary2Source.update(new Date());
233
+
234
+ // Binary 1 available, binary 2 available and more recent and in next check date/time window.
235
+ await expect(GCPLength.loadData(gcpLengthCacheBinary2Source).then((root) => {
236
+ root2 = root;
237
+ })).resolves.not.toThrowError(RangeError);
238
+
239
+ expect(root2).not.toBeUndefined();
240
+
241
+ // Load from binary 1 not equal to load from binary 2.
242
+ expect(() => {
243
+ verifyEqual("", root1, root2);
244
+ }).toThrow(RangeError);
245
+ });
246
+
247
+ test("Identifiers", async () => {
248
+ const gcpLengthCacheJSON2Source = new class extends GCPLengthCacheJSONSource {
249
+ override get nextCheckDateTime(): Promise<Date | undefined> {
250
+ return Promise.resolve(undefined);
251
+ }
252
+
253
+ override get cacheDateTime(): Promise<Date | undefined> {
254
+ return Promise.resolve(undefined);
255
+ }
256
+
257
+ override async update(): Promise<void> {
258
+ return Promise.resolve();
259
+ }
260
+ }(new (await LocalAppDataStorage)(DATA_DIRECTORY), 2);
261
+
262
+ const tempRoot = await GCPLength.loadData(gcpLengthCacheJSON2Source);
263
+
264
+ expect(tempRoot).not.toBeUndefined();
265
+
266
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Known to be defined.
267
+ const root = tempRoot!;
268
+
269
+ function testIdentifiers(prefixManager: PrefixManager, prefixLength: number): void {
270
+ expect(GCPLength.getFor(root, IdentifierTypes.GTIN, prefixManager.gtinCreator.create(0, true))).toBe(prefixLength);
271
+ expect(GCPLength.getFor(root, IdentifierTypes.GTIN, prefixManager.gtinCreator.createGTIN14("4", 0, true))).toBe(prefixLength);
272
+
273
+ if (prefixManager.prefixType !== PrefixTypes.GS18Prefix) {
274
+ expect(GCPLength.getFor(root, IdentifierTypes.GLN, prefixManager.glnCreator.create(0, true))).toBe(prefixLength);
275
+ expect(GCPLength.getFor(root, IdentifierTypes.SSCC, prefixManager.ssccCreator.create(0, true))).toBe(prefixLength);
276
+ expect(GCPLength.getFor(root, IdentifierTypes.GRAI, prefixManager.graiCreator.create(0, true))).toBe(prefixLength);
277
+ expect(GCPLength.getFor(root, IdentifierTypes.GRAI, prefixManager.graiCreator.createSerialized(0, "ABCD1234", true))).toBe(prefixLength);
278
+ expect(GCPLength.getFor(root, IdentifierTypes.GIAI, prefixManager.giaiCreator.create("EFGH5678"))).toBe(prefixLength);
279
+ }
280
+ }
281
+
282
+ const gcpLengthJSON = gcpLengthCacheJSON2Source.sourceData;
283
+ const entries = gcpLengthJSON.GCPPrefixFormatList.entry;
284
+
285
+ for (let i = 0; i < 1000; i++) {
286
+ const entryIndex = Math.floor(Math.random() * entries.length);
287
+ const gcpLengthJSONEntry = entries[entryIndex];
288
+ const prefixLength = gcpLengthJSONEntry.gcpLength;
289
+ const testPrefixLength = prefixLength !== 0 ? prefixLength : Math.max(PrefixValidator.GS1_COMPANY_PREFIX_MINIMUM_LENGTH, PrefixValidator.UPC_COMPANY_PREFIX_MINIMUM_LENGTH + 1, PrefixValidator.GS1_8_PREFIX_MINIMUM_LENGTH + 5);
290
+
291
+ let prefix = gcpLengthJSONEntry.prefix;
292
+
293
+ while (prefix.length !== testPrefixLength) {
294
+ prefix = `${prefix}${Math.floor(Math.random() * 10)}`;
295
+ }
296
+
297
+ testIdentifiers(PrefixManager.get(PrefixTypes.GS1CompanyPrefix, prefix), prefixLength);
298
+
299
+ if (entryIndex !== entries.length - 1) {
300
+ const nextGCPLengthJSONEntry = entries[entryIndex + 1];
301
+
302
+ if (nextGCPLengthJSONEntry.prefix.length === gcpLengthJSONEntry.prefix.length && Number(nextGCPLengthJSONEntry.prefix) - Number(gcpLengthJSONEntry.prefix) !== 1) {
303
+ let nextPrefix = String(Number(gcpLengthJSONEntry.prefix) + 1).padStart(gcpLengthJSONEntry.prefix.length, "0");
304
+
305
+ while (nextPrefix.length !== testPrefixLength) {
306
+ nextPrefix = `${nextPrefix}${Math.floor(Math.random() * 10)}`;
307
+ }
308
+
309
+ testIdentifiers(PrefixManager.get(PrefixTypes.GS1CompanyPrefix, nextPrefix), -1);
310
+ }
311
+ }
312
+ }
313
+ });
314
+
315
+ test("Remote", async () => {
316
+ let savedNextCheckDateTime: Date | undefined = undefined;
317
+ let savedCacheDateTime: Date | undefined = undefined;
318
+ let savedCacheData: GCPLengthData | undefined = undefined;
319
+
320
+ const gcpLengthCache = new class extends RemoteGCPLengthCache {
321
+ override get nextCheckDateTime(): Promise<Date | undefined> {
322
+ return Promise.resolve(savedNextCheckDateTime);
323
+ }
324
+
325
+ override get cacheDateTime(): Promise<Date | undefined> {
326
+ return Promise.resolve(savedCacheDateTime);
327
+ }
328
+
329
+ override async update(nextCheckDateTime: Date, cacheDateTime?: Date, cacheData?: GCPLengthData): Promise<void> {
330
+ savedNextCheckDateTime = nextCheckDateTime;
331
+ savedCacheDateTime = cacheDateTime;
332
+ savedCacheData = cacheData;
333
+
334
+ return Promise.resolve();
335
+ }
336
+ }(new (await LocalAppDataStorage)(DATA_DIRECTORY));
337
+
338
+ await expect(GCPLength.loadData(gcpLengthCache)).resolves.not.toThrowError(RangeError);
339
+
340
+ expect(savedNextCheckDateTime).not.toBeUndefined();
341
+ expect(savedCacheDateTime).not.toBeUndefined();
342
+ expect(savedCacheData).not.toBeUndefined();
343
+ });
344
+ });
@@ -68,8 +68,8 @@ export function testGTINCreator(creator: GTINCreator): void {
68
68
 
69
69
  expect(index).toBe(referenceCount);
70
70
 
71
- const randomValues = new Array<number>();
72
- const identifiers = new Array<string>();
71
+ const randomValues: number[] = [];
72
+ const identifiers: string[] = [];
73
73
 
74
74
  for (let i = 0; i < 1000; i++) {
75
75
  const randomValue = Math.floor(Math.random() * creator.capacity);
@@ -105,8 +105,8 @@ export function testGTINCreator(creator: GTINCreator): void {
105
105
  expect(sequential).toBe(false);
106
106
  expect(index).toBe(sparseReferenceCount);
107
107
 
108
- const randomValues = new Array<number>();
109
- const identifiers = new Array<string>();
108
+ const randomValues: number[] = [];
109
+ const identifiers: string[] = [];
110
110
 
111
111
  for (let i = 0; i < 1000; i++) {
112
112
  const randomValue = Math.floor(Math.random() * creator.capacity);
@@ -1,122 +1,214 @@
1
1
  import { describe, expect, test } from "vitest";
2
- import {
3
- type GTINBaseLength,
4
- GTINLengths,
5
- GTINLevels,
6
- GTINValidator,
7
- IdentifierTypes,
8
- LeaderTypes,
9
- type PrefixType,
10
- PrefixTypes
11
- } from "../src/index.js";
12
- import { validateNumericIdentifierValidator } from "./numeric-identifier-validator.js";
13
-
14
- export function validateGTINValidator(validator: GTINValidator, isCreator: boolean, gtinBaseLength: GTINBaseLength): void {
15
- let prefixType: PrefixType;
16
-
17
- switch (gtinBaseLength) {
18
- case GTINLengths.GTIN13:
19
- prefixType = PrefixTypes.GS1CompanyPrefix;
20
- break;
21
-
22
- case GTINLengths.GTIN12:
23
- prefixType = PrefixTypes.UPCCompanyPrefix;
24
- break;
25
-
26
- case GTINLengths.GTIN8:
27
- prefixType = PrefixTypes.GS18Prefix;
28
- break;
29
- }
2
+ import { type GTINLevel, GTINLevels, GTINValidator } from "../src/index.js";
30
3
 
31
- validateNumericIdentifierValidator(validator, IdentifierTypes.GTIN, prefixType, gtinBaseLength, LeaderTypes.IndicatorDigit);
4
+ interface GTINValidation {
5
+ gtin: string;
6
+
7
+ errorMessages: Record<GTINLevel, string | null>;
32
8
  }
33
9
 
10
+ const GTIN_VALIDATIONS: GTINValidation[] = [
11
+ // GTIN-13.
12
+ {
13
+ gtin: "9521873000122",
14
+ errorMessages: {
15
+ [GTINLevels.Any]: null,
16
+ [GTINLevels.RetailConsumer]: null,
17
+ [GTINLevels.OtherThanRetailConsumer]: null
18
+ }
19
+ },
20
+
21
+ // GTIN-13 as GTIN-14.
22
+ {
23
+ gtin: "09521873000122",
24
+ errorMessages: {
25
+ [GTINLevels.Any]: null,
26
+ [GTINLevels.RetailConsumer]: "GTIN not supported at retail consumer trade item level",
27
+ [GTINLevels.OtherThanRetailConsumer]: null
28
+ }
29
+ },
30
+
31
+ // GTIN-12.
32
+ {
33
+ gtin: "614141773985",
34
+ errorMessages: {
35
+ [GTINLevels.Any]: null,
36
+ [GTINLevels.RetailConsumer]: null,
37
+ [GTINLevels.OtherThanRetailConsumer]: null
38
+ }
39
+ },
40
+
41
+ // GTIN-12 as GTIN-13.
42
+ {
43
+ gtin: "0614141773985",
44
+ errorMessages: {
45
+ [GTINLevels.Any]: null,
46
+ [GTINLevels.RetailConsumer]: "GTIN not supported at retail consumer trade item level",
47
+ [GTINLevels.OtherThanRetailConsumer]: null
48
+ }
49
+ },
50
+
51
+ // GTIN-12 as GTIN-14.
52
+ {
53
+ gtin: "00614141773985",
54
+ errorMessages: {
55
+ [GTINLevels.Any]: null,
56
+ [GTINLevels.RetailConsumer]: "GTIN not supported at retail consumer trade item level",
57
+ [GTINLevels.OtherThanRetailConsumer]: null
58
+ }
59
+ },
60
+
61
+ // Zero-suppressed GTIN-12.
62
+ {
63
+ gtin: "09867539",
64
+ errorMessages: {
65
+ [GTINLevels.Any]: null,
66
+ [GTINLevels.RetailConsumer]: null,
67
+ [GTINLevels.OtherThanRetailConsumer]: "GTIN not supported at other than retail consumer trade item level"
68
+ }
69
+ },
70
+
71
+ // GTIN-8.
72
+ {
73
+ gtin: "95216843",
74
+ errorMessages: {
75
+ [GTINLevels.Any]: null,
76
+ [GTINLevels.RetailConsumer]: null,
77
+ [GTINLevels.OtherThanRetailConsumer]: "GTIN not supported at other than retail consumer trade item level"
78
+ }
79
+ },
80
+
81
+ // GTIN-8 as GTIN-12.
82
+ {
83
+ gtin: "000095216843",
84
+ errorMessages: {
85
+ [GTINLevels.Any]: "U.P.C. Company Prefix can't start with \"0000\"",
86
+ [GTINLevels.RetailConsumer]: "U.P.C. Company Prefix can't start with \"0000\"",
87
+ [GTINLevels.OtherThanRetailConsumer]: "U.P.C. Company Prefix can't start with \"0000\""
88
+ }
89
+ },
90
+
91
+ // GTIN-8 as GTIN-13.
92
+ {
93
+ gtin: "0000095216843",
94
+ errorMessages: {
95
+ [GTINLevels.Any]: null,
96
+ [GTINLevels.RetailConsumer]: "GTIN not supported at retail consumer trade item level",
97
+ [GTINLevels.OtherThanRetailConsumer]: "GTIN not supported at other than retail consumer trade item level"
98
+ }
99
+ },
100
+
101
+ // GTIN-8 as GTIN-14.
102
+ {
103
+ gtin: "00000095216843",
104
+ errorMessages: {
105
+ [GTINLevels.Any]: null,
106
+ [GTINLevels.RetailConsumer]: "GTIN not supported at retail consumer trade item level",
107
+ [GTINLevels.OtherThanRetailConsumer]: "GTIN not supported at other than retail consumer trade item level"
108
+ }
109
+ },
110
+
111
+ // GTIN-14 from GTIN-13.
112
+ {
113
+ gtin: "19521873000129",
114
+ errorMessages: {
115
+ [GTINLevels.Any]: null,
116
+ [GTINLevels.RetailConsumer]: "GTIN not supported at retail consumer trade item level",
117
+ [GTINLevels.OtherThanRetailConsumer]: null
118
+ }
119
+ },
120
+
121
+ // GTIN-14 from GTIN-12.
122
+ {
123
+ gtin: "20614141773989",
124
+ errorMessages: {
125
+ [GTINLevels.Any]: null,
126
+ [GTINLevels.RetailConsumer]: "GTIN not supported at retail consumer trade item level",
127
+ [GTINLevels.OtherThanRetailConsumer]: null
128
+ }
129
+ },
130
+
131
+ // GTIN-14 from GTIN-8.
132
+ {
133
+ gtin: "30000095216844",
134
+ errorMessages: {
135
+ [GTINLevels.Any]: null,
136
+ [GTINLevels.RetailConsumer]: "GTIN not supported at retail consumer trade item level",
137
+ [GTINLevels.OtherThanRetailConsumer]: null
138
+ }
139
+ },
140
+
141
+ // Invalid character in GTIN-13.
142
+ {
143
+ gtin: "9521873000l22",
144
+ errorMessages: {
145
+ [GTINLevels.Any]: "Invalid character 'l' at position 11",
146
+ [GTINLevels.RetailConsumer]: "Invalid character 'l' at position 11",
147
+ [GTINLevels.OtherThanRetailConsumer]: "Invalid character 'l' at position 11"
148
+ }
149
+ },
150
+
151
+ // Invalid character in GTIN-14.
152
+ {
153
+ gtin: "30000O95216844",
154
+ errorMessages: {
155
+ [GTINLevels.Any]: "Invalid character 'O' at position 6",
156
+ [GTINLevels.RetailConsumer]: "Invalid character 'O' at position 6",
157
+ [GTINLevels.OtherThanRetailConsumer]: "Invalid character 'O' at position 6"
158
+ }
159
+ },
160
+
161
+ // Invalid length.
162
+ {
163
+ gtin: "95217800031",
164
+ errorMessages: {
165
+ [GTINLevels.Any]: "GTIN must be 13, 12, 8, or 14 digits long",
166
+ [GTINLevels.RetailConsumer]: "GTIN must be 13, 12, 8, or 14 digits long",
167
+ [GTINLevels.OtherThanRetailConsumer]: "GTIN must be 13, 12, 8, or 14 digits long"
168
+ }
169
+ },
170
+
171
+ // Invalid check digit.
172
+ {
173
+ gtin: "614141773991",
174
+ errorMessages: {
175
+ [GTINLevels.Any]: "Invalid check digit",
176
+ [GTINLevels.RetailConsumer]: "Invalid check digit",
177
+ [GTINLevels.OtherThanRetailConsumer]: "Invalid check digit"
178
+ }
179
+ },
180
+
181
+ // Invalid zero-suppressed GTIN-12.
182
+ {
183
+ gtin: "09800037",
184
+ errorMessages: {
185
+ [GTINLevels.Any]: "Invalid zero-suppressed GTIN-12",
186
+ [GTINLevels.RetailConsumer]: "Invalid zero-suppressed GTIN-12",
187
+ [GTINLevels.OtherThanRetailConsumer]: "Invalid zero-suppressed GTIN-12"
188
+ }
189
+ }
190
+ ];
191
+
34
192
  describe("GTIN validation and normalization", () => {
35
193
  test("Validation", () => {
36
- expect(() => {
37
- GTINValidator.validateAny("9521873000122", GTINLevels.Any);
38
- }).not.toThrow(RangeError);
39
- expect(() => {
40
- GTINValidator.validateAny("19521873000129", GTINLevels.Any);
41
- }).not.toThrow(RangeError);
42
- expect(() => {
43
- GTINValidator.validateAny("9521873000160", GTINLevels.Any);
44
- }).not.toThrow(RangeError);
45
- expect(() => {
46
- GTINValidator.validateAny("95216843", GTINLevels.Any);
47
- }).not.toThrow(RangeError);
48
- expect(() => {
49
- GTINValidator.validateAny("95217800031", GTINLevels.Any);
50
- }).toThrow("GTIN must be 13, 12, 8, or 14 digits long");
51
- expect(() => {
52
- GTINValidator.validateAny("614141773985", GTINLevels.Any);
53
- }).not.toThrow(RangeError);
54
- expect(() => {
55
- GTINValidator.validateAny("614141773991", GTINLevels.Any);
56
- }).toThrow("Invalid check digit");
57
- expect(() => {
58
- GTINValidator.validateAny("09867539", GTINLevels.Any);
59
- }).not.toThrow(RangeError);
60
- expect(() => {
61
- GTINValidator.validateAny("09800037", GTINLevels.Any);
62
- }).toThrow("Invalid zero-suppressed GTIN-12");
63
- expect(() => {
64
- GTINValidator.validateAny("9521873000122", GTINLevels.RetailConsumer);
65
- }).not.toThrow(RangeError);
66
- expect(() => {
67
- GTINValidator.validateAny("19521873000129", GTINLevels.RetailConsumer);
68
- }).toThrow("GTIN not supported at retail consumer trade item level");
69
- expect(() => {
70
- GTINValidator.validateAny("9521873000160", GTINLevels.RetailConsumer);
71
- }).not.toThrow(RangeError);
72
- expect(() => {
73
- GTINValidator.validateAny("95216843", GTINLevels.RetailConsumer);
74
- }).not.toThrow(RangeError);
75
- expect(() => {
76
- GTINValidator.validateAny("95217800031", GTINLevels.RetailConsumer);
77
- }).toThrow("GTIN must be 13, 12, 8, or 14 digits long");
78
- expect(() => {
79
- GTINValidator.validateAny("614141773985", GTINLevels.RetailConsumer);
80
- }).not.toThrow(RangeError);
81
- expect(() => {
82
- GTINValidator.validateAny("0614141773985", GTINLevels.RetailConsumer);
83
- }).toThrow("GTIN-13 at retail consumer trade item level can't start with zero");
84
- expect(() => {
85
- GTINValidator.validateAny("614141773991", GTINLevels.RetailConsumer);
86
- }).toThrow("Invalid check digit");
87
- expect(() => {
88
- GTINValidator.validateAny("09867539", GTINLevels.RetailConsumer);
89
- }).not.toThrow(RangeError);
90
- expect(() => {
91
- GTINValidator.validateAny("09800037", GTINLevels.RetailConsumer);
92
- }).toThrow("Invalid zero-suppressed GTIN-12");
93
- expect(() => {
94
- GTINValidator.validateAny("9521873000122", GTINLevels.OtherThanRetailConsumer);
95
- }).not.toThrow(RangeError);
96
- expect(() => {
97
- GTINValidator.validateAny("19521873000129", GTINLevels.OtherThanRetailConsumer);
98
- }).not.toThrow(RangeError);
99
- expect(() => {
100
- GTINValidator.validateAny("9521873000160", GTINLevels.OtherThanRetailConsumer);
101
- }).not.toThrow(RangeError);
102
- expect(() => {
103
- GTINValidator.validateAny("95216843", GTINLevels.OtherThanRetailConsumer);
104
- }).toThrow("GTIN not supported at other than retail consumer trade item level");
105
- expect(() => {
106
- GTINValidator.validateAny("95217800031", GTINLevels.OtherThanRetailConsumer);
107
- }).toThrow("GTIN must be 13, 12, 8, or 14 digits long");
108
- expect(() => {
109
- GTINValidator.validateAny("614141773985", GTINLevels.OtherThanRetailConsumer);
110
- }).not.toThrow(RangeError);
111
- expect(() => {
112
- GTINValidator.validateAny("614141773991", GTINLevels.OtherThanRetailConsumer);
113
- }).toThrow("Invalid check digit");
114
- expect(() => {
115
- GTINValidator.validateAny("09867539", GTINLevels.OtherThanRetailConsumer);
116
- }).toThrow("GTIN not supported at other than retail consumer trade item level");
117
- expect(() => {
118
- GTINValidator.validateAny("09800037", GTINLevels.OtherThanRetailConsumer);
119
- }).toThrow("Invalid zero-suppressed GTIN-12");
194
+ for (const gtinValidation of GTIN_VALIDATIONS) {
195
+ const gtin = gtinValidation.gtin;
196
+
197
+ for (const gtinLevel of Object.values(GTINLevels)) {
198
+ const expectMessage = `${gtin}:${gtinLevel}`;
199
+ const errorMessage = gtinValidation.errorMessages[gtinLevel];
200
+
201
+ if (errorMessage === null) {
202
+ expect(() => {
203
+ GTINValidator.validateAny(gtin, gtinLevel);
204
+ }, expectMessage).not.toThrow(RangeError);
205
+ } else {
206
+ expect(() => {
207
+ GTINValidator.validateAny(gtin, gtinLevel);
208
+ }, expectMessage).toThrow(errorMessage);
209
+ }
210
+ }
211
+ }
120
212
  });
121
213
 
122
214
  test("Normalization", () => {