@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.
- package/README.md +290 -17
- package/dist/character-set.d.ts +17 -2
- package/dist/character-set.d.ts.map +1 -1
- package/dist/character-set.js +44 -11
- package/dist/character-set.js.map +1 -1
- package/dist/gcp-length-cache.d.ts +81 -0
- package/dist/gcp-length-cache.d.ts.map +1 -0
- package/dist/gcp-length-cache.js +232 -0
- package/dist/gcp-length-cache.js.map +1 -0
- package/dist/gcp-length-data.d.ts +108 -0
- package/dist/gcp-length-data.d.ts.map +1 -0
- package/dist/gcp-length-data.js +53 -0
- package/dist/gcp-length-data.js.map +1 -0
- package/dist/gcp-length.d.ts +61 -0
- package/dist/gcp-length.d.ts.map +1 -0
- package/dist/gcp-length.js +300 -0
- package/dist/gcp-length.js.map +1 -0
- package/dist/gtin-creator.d.ts +0 -17
- package/dist/gtin-creator.d.ts.map +1 -1
- package/dist/gtin-creator.js +1 -93
- package/dist/gtin-creator.js.map +1 -1
- package/dist/gtin-validator.d.ts +1 -46
- package/dist/gtin-validator.d.ts.map +1 -1
- package/dist/gtin-validator.js +31 -125
- package/dist/gtin-validator.js.map +1 -1
- package/dist/identifier-validator.d.ts +1 -4
- package/dist/identifier-validator.d.ts.map +1 -1
- package/dist/identifier-validator.js +2 -5
- package/dist/identifier-validator.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/locale/en/locale-resources.d.ts +39 -35
- package/dist/locale/en/locale-resources.d.ts.map +1 -1
- package/dist/locale/en/locale-resources.js +6 -2
- package/dist/locale/en/locale-resources.js.map +1 -1
- package/dist/locale/fr/locale-resources.d.ts +39 -35
- package/dist/locale/fr/locale-resources.d.ts.map +1 -1
- package/dist/locale/fr/locale-resources.js +6 -2
- package/dist/locale/fr/locale-resources.js.map +1 -1
- package/dist/locale/i18n.d.ts +4 -4
- package/dist/locale/i18n.d.ts.map +1 -1
- package/dist/locale/i18n.js +6 -7
- package/dist/locale/i18n.js.map +1 -1
- package/dist/non-numeric-identifier-validator.js +1 -1
- package/dist/non-numeric-identifier-validator.js.map +1 -1
- package/dist/numeric-identifier-validator.d.ts.map +1 -1
- package/dist/numeric-identifier-validator.js +2 -2
- package/dist/numeric-identifier-validator.js.map +1 -1
- package/dist/prefix-manager.d.ts +35 -0
- package/dist/prefix-manager.d.ts.map +1 -1
- package/dist/prefix-manager.js +56 -0
- package/dist/prefix-manager.js.map +1 -1
- package/dist/prefix-validator.d.ts +5 -4
- package/dist/prefix-validator.d.ts.map +1 -1
- package/dist/prefix-validator.js +18 -22
- package/dist/prefix-validator.js.map +1 -1
- package/dist/serializable-numeric-identifier-validator.d.ts +26 -0
- package/dist/serializable-numeric-identifier-validator.d.ts.map +1 -1
- package/dist/serializable-numeric-identifier-validator.js +19 -0
- package/dist/serializable-numeric-identifier-validator.js.map +1 -1
- package/dist/variable-measure.d.ts +68 -0
- package/dist/variable-measure.d.ts.map +1 -0
- package/dist/variable-measure.js +210 -0
- package/dist/variable-measure.js.map +1 -0
- package/dist/verified-by-gs1.d.ts +22 -0
- package/dist/verified-by-gs1.d.ts.map +1 -0
- package/dist/verified-by-gs1.js +46 -0
- package/dist/verified-by-gs1.js.map +1 -0
- package/package.json +12 -11
- package/src/character-set.ts +55 -11
- package/src/gcp-length-cache.ts +271 -0
- package/src/gcp-length-data.ts +136 -0
- package/src/gcp-length.ts +380 -0
- package/src/gtin-creator.ts +1 -117
- package/src/gtin-validator.ts +42 -173
- package/src/identifier-validator.ts +2 -5
- package/src/index.ts +7 -1
- package/src/locale/en/locale-resources.ts +7 -3
- package/src/locale/fr/locale-resources.ts +7 -3
- package/src/locale/i18n.ts +7 -8
- package/src/locale/i18next.d.ts +2 -0
- package/src/non-numeric-identifier-validator.ts +1 -1
- package/src/numeric-identifier-validator.ts +3 -3
- package/src/prefix-manager.ts +65 -0
- package/src/prefix-validator.ts +19 -23
- package/src/serializable-numeric-identifier-validator.ts +36 -0
- package/src/variable-measure.ts +268 -0
- package/src/verified-by-gs1.ts +54 -0
- package/test/character-set.test.ts +46 -0
- package/test/creator.test.ts +5 -5
- package/test/data/gcpprefixformatlist-1.json +662625 -0
- package/test/data/gcpprefixformatlist-2.json +735431 -0
- package/test/gcp-length.test.ts +344 -0
- package/test/gtin-creator.ts +4 -4
- package/test/gtin-validator.test.ts +205 -113
- package/test/gtin-validator.ts +30 -0
- package/test/identifier-creator.ts +6 -6
- package/test/non-numeric-identifier-creator.ts +0 -8
- package/test/non-serializable-numeric-identifier-creator.ts +4 -54
- package/test/numeric-identifier-creator.ts +4 -4
- package/test/prefix-manager.test.ts +5 -5
- package/test/serializable-numeric-identifier-creator.ts +32 -19
- package/test/validator.test.ts +6 -6
- package/test/variable-measure-rcn.test.ts +63 -68
- package/test/verified-by-gs1.test.ts +55 -0
- package/tsconfig-src.json +7 -1
- package/tsconfig-src.tsbuildinfo +1 -0
- 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
|
+
});
|
package/test/gtin-creator.ts
CHANGED
|
@@ -68,8 +68,8 @@ export function testGTINCreator(creator: GTINCreator): void {
|
|
|
68
68
|
|
|
69
69
|
expect(index).toBe(referenceCount);
|
|
70
70
|
|
|
71
|
-
const randomValues =
|
|
72
|
-
const identifiers =
|
|
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 =
|
|
109
|
-
const identifiers =
|
|
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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
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", () => {
|