@nfcard/validation 0.16.0 → 0.18.0
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/dist/index.js +0 -0
- package/package.json +2 -2
- package/src/elements.test.ts +16 -0
- package/src/index.test.ts +101 -101
- package/src/index.ts +0 -0
package/dist/index.js
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nfcard/validation",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Shared Zod validation schemas for the NFCard product family.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"zod": "^3.24.0",
|
|
20
|
-
"@nfcard/types": "0.13.
|
|
20
|
+
"@nfcard/types": "0.13.2"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"tsup": "^8.0.0"
|
package/src/elements.test.ts
CHANGED
|
@@ -81,6 +81,22 @@ describe('cardDesignSchema — free-transform element layer (NFCARD-197/198)', (
|
|
|
81
81
|
expect(res.success).toBe(true)
|
|
82
82
|
})
|
|
83
83
|
|
|
84
|
+
it('NFCARD-480: accepts a HIDDEN chip (shape -1) — the order gate must not 400 it', () => {
|
|
85
|
+
const res = cardDesignSchema.safeParse(withElements([chipEl({ shape: -1 }), textEl(), qrEl()]))
|
|
86
|
+
expect(res.success).toBe(true)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('NFCARD-480: a CELL never takes the hidden sentinel (cellShape -1 still rejected)', () => {
|
|
90
|
+
const validPattern = {
|
|
91
|
+
family: 'spiral', variant: 'pulse', density: 0.3, holeMinMm: 0.5, holeMaxMm: 1.8,
|
|
92
|
+
intensity: 0.5, cellShape: 0, cellRotationDeg: 0, chipShape: 0, chipRotationDeg: 0,
|
|
93
|
+
}
|
|
94
|
+
expect(cardDesignSchema.safeParse(withElements([chipEl()], { pattern: validPattern })).success).toBe(true)
|
|
95
|
+
const res = cardDesignSchema.safeParse(withElements([chipEl()], { pattern: { ...validPattern, cellShape: -1 } }))
|
|
96
|
+
expect(res.success).toBe(false)
|
|
97
|
+
expect(codes(res)).toContain('cellSidesRange')
|
|
98
|
+
})
|
|
99
|
+
|
|
84
100
|
it('rejects a QR below the scannable size floor', () => {
|
|
85
101
|
const res = cardDesignSchema.safeParse(withElements([chipEl(), qrEl({ sizeMm: 12 })]))
|
|
86
102
|
expect(res.success).toBe(false)
|
package/src/index.test.ts
CHANGED
|
@@ -974,104 +974,104 @@ describe('physicalConfig.cardDesign (nested, not stripped)', () => {
|
|
|
974
974
|
expect(physicalConfigSchema.safeParse(validPhysicalConfig).success).toBe(true);
|
|
975
975
|
});
|
|
976
976
|
});
|
|
977
|
-
|
|
978
|
-
// -- entrySourceSchema (NFCARD-392) --------------------------------
|
|
979
|
-
|
|
980
|
-
describe('entrySourceSchema on configurationCreateSchema', () => {
|
|
981
|
-
const base = {
|
|
982
|
-
sessionId: '550e8400-e29b-41d4-a716-446655440000',
|
|
983
|
-
userData: validUserData,
|
|
984
|
-
physicalConfig: validPhysicalConfig,
|
|
985
|
-
digitalConfig: validDigitalConfig,
|
|
986
|
-
};
|
|
987
|
-
|
|
988
|
-
it('accepts a full entry-attribution blob', () => {
|
|
989
|
-
const r = configurationCreateSchema.safeParse({
|
|
990
|
-
...base,
|
|
991
|
-
entrySource: {
|
|
992
|
-
output: 'with-card',
|
|
993
|
-
entryFlow: 'order',
|
|
994
|
-
ref: 'PROMO24',
|
|
995
|
-
path: '/configurator',
|
|
996
|
-
referrer: 'google.com',
|
|
997
|
-
utm: { utm_source: 'ads', utm_campaign: 'launch' },
|
|
998
|
-
},
|
|
999
|
-
});
|
|
1000
|
-
expect(r.success).toBe(true);
|
|
1001
|
-
if (r.success) expect(r.data.entrySource?.output).toBe('with-card');
|
|
1002
|
-
});
|
|
1003
|
-
|
|
1004
|
-
it('is optional — older clients omit it entirely', () => {
|
|
1005
|
-
const r = configurationCreateSchema.safeParse(base);
|
|
1006
|
-
expect(r.success).toBe(true);
|
|
1007
|
-
if (r.success) expect(r.data.entrySource).toBeUndefined();
|
|
1008
|
-
});
|
|
1009
|
-
|
|
1010
|
-
it('rejects unknown keys (strict) and out-of-enum values', () => {
|
|
1011
|
-
expect(
|
|
1012
|
-
configurationCreateSchema.safeParse({
|
|
1013
|
-
...base,
|
|
1014
|
-
entrySource: { output: 'with-card', evil: 'payload' },
|
|
1015
|
-
}).success,
|
|
1016
|
-
).toBe(false);
|
|
1017
|
-
expect(
|
|
1018
|
-
configurationCreateSchema.safeParse({
|
|
1019
|
-
...base,
|
|
1020
|
-
entrySource: { output: 'both-please' },
|
|
1021
|
-
}).success,
|
|
1022
|
-
).toBe(false);
|
|
1023
|
-
});
|
|
1024
|
-
|
|
1025
|
-
it('bounds the utm map (max 10 entries, capped value length)', () => {
|
|
1026
|
-
const utm: Record<string, string> = {};
|
|
1027
|
-
for (let i = 0; i < 11; i++) utm['k' + i] = 'v';
|
|
1028
|
-
expect(
|
|
1029
|
-
configurationCreateSchema.safeParse({ ...base, entrySource: { utm } }).success,
|
|
1030
|
-
).toBe(false);
|
|
1031
|
-
expect(
|
|
1032
|
-
configurationCreateSchema.safeParse({
|
|
1033
|
-
...base,
|
|
1034
|
-
entrySource: { referrer: 'x'.repeat(300) },
|
|
1035
|
-
}).success,
|
|
1036
|
-
).toBe(false);
|
|
1037
|
-
});
|
|
1038
|
-
|
|
1039
|
-
it('is create-only: the update schema strips/ignores it', () => {
|
|
1040
|
-
const r = configurationUpdateSchema.safeParse({
|
|
1041
|
-
entrySource: { output: 'with-card' },
|
|
1042
|
-
});
|
|
1043
|
-
// z.object strips unknown keys by default - the field must not survive.
|
|
1044
|
-
expect(r.success).toBe(true);
|
|
1045
|
-
if (r.success) expect((r.data as Record<string, unknown>).entrySource).toBeUndefined();
|
|
1046
|
-
});
|
|
1047
|
-
});
|
|
1048
|
-
|
|
1049
|
-
describe('entrySourceSchema material/template preselects (NFCARD-393)', () => {
|
|
1050
|
-
const base = {
|
|
1051
|
-
sessionId: '550e8400-e29b-41d4-a716-446655440000',
|
|
1052
|
-
userData: validUserData,
|
|
1053
|
-
physicalConfig: validPhysicalConfig,
|
|
1054
|
-
digitalConfig: validDigitalConfig,
|
|
1055
|
-
};
|
|
1056
|
-
|
|
1057
|
-
it('accepts the deep-link preselects', () => {
|
|
1058
|
-
const r = configurationCreateSchema.safeParse({
|
|
1059
|
-
...base,
|
|
1060
|
-
entrySource: { output: 'with-card', material: '3dprint_standard', template: 'tpl-pvc-white' },
|
|
1061
|
-
});
|
|
1062
|
-
expect(r.success).toBe(true);
|
|
1063
|
-
if (r.success) {
|
|
1064
|
-
expect(r.data.entrySource?.material).toBe('3dprint_standard');
|
|
1065
|
-
expect(r.data.entrySource?.template).toBe('tpl-pvc-white');
|
|
1066
|
-
}
|
|
1067
|
-
});
|
|
1068
|
-
|
|
1069
|
-
it('rejects a non-enum material', () => {
|
|
1070
|
-
expect(
|
|
1071
|
-
configurationCreateSchema.safeParse({
|
|
1072
|
-
...base,
|
|
1073
|
-
entrySource: { material: 'carbon-fiber' },
|
|
1074
|
-
}).success,
|
|
1075
|
-
).toBe(false);
|
|
1076
|
-
});
|
|
1077
|
-
});
|
|
977
|
+
|
|
978
|
+
// -- entrySourceSchema (NFCARD-392) --------------------------------
|
|
979
|
+
|
|
980
|
+
describe('entrySourceSchema on configurationCreateSchema', () => {
|
|
981
|
+
const base = {
|
|
982
|
+
sessionId: '550e8400-e29b-41d4-a716-446655440000',
|
|
983
|
+
userData: validUserData,
|
|
984
|
+
physicalConfig: validPhysicalConfig,
|
|
985
|
+
digitalConfig: validDigitalConfig,
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
it('accepts a full entry-attribution blob', () => {
|
|
989
|
+
const r = configurationCreateSchema.safeParse({
|
|
990
|
+
...base,
|
|
991
|
+
entrySource: {
|
|
992
|
+
output: 'with-card',
|
|
993
|
+
entryFlow: 'order',
|
|
994
|
+
ref: 'PROMO24',
|
|
995
|
+
path: '/configurator',
|
|
996
|
+
referrer: 'google.com',
|
|
997
|
+
utm: { utm_source: 'ads', utm_campaign: 'launch' },
|
|
998
|
+
},
|
|
999
|
+
});
|
|
1000
|
+
expect(r.success).toBe(true);
|
|
1001
|
+
if (r.success) expect(r.data.entrySource?.output).toBe('with-card');
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
it('is optional — older clients omit it entirely', () => {
|
|
1005
|
+
const r = configurationCreateSchema.safeParse(base);
|
|
1006
|
+
expect(r.success).toBe(true);
|
|
1007
|
+
if (r.success) expect(r.data.entrySource).toBeUndefined();
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
it('rejects unknown keys (strict) and out-of-enum values', () => {
|
|
1011
|
+
expect(
|
|
1012
|
+
configurationCreateSchema.safeParse({
|
|
1013
|
+
...base,
|
|
1014
|
+
entrySource: { output: 'with-card', evil: 'payload' },
|
|
1015
|
+
}).success,
|
|
1016
|
+
).toBe(false);
|
|
1017
|
+
expect(
|
|
1018
|
+
configurationCreateSchema.safeParse({
|
|
1019
|
+
...base,
|
|
1020
|
+
entrySource: { output: 'both-please' },
|
|
1021
|
+
}).success,
|
|
1022
|
+
).toBe(false);
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
it('bounds the utm map (max 10 entries, capped value length)', () => {
|
|
1026
|
+
const utm: Record<string, string> = {};
|
|
1027
|
+
for (let i = 0; i < 11; i++) utm['k' + i] = 'v';
|
|
1028
|
+
expect(
|
|
1029
|
+
configurationCreateSchema.safeParse({ ...base, entrySource: { utm } }).success,
|
|
1030
|
+
).toBe(false);
|
|
1031
|
+
expect(
|
|
1032
|
+
configurationCreateSchema.safeParse({
|
|
1033
|
+
...base,
|
|
1034
|
+
entrySource: { referrer: 'x'.repeat(300) },
|
|
1035
|
+
}).success,
|
|
1036
|
+
).toBe(false);
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
it('is create-only: the update schema strips/ignores it', () => {
|
|
1040
|
+
const r = configurationUpdateSchema.safeParse({
|
|
1041
|
+
entrySource: { output: 'with-card' },
|
|
1042
|
+
});
|
|
1043
|
+
// z.object strips unknown keys by default - the field must not survive.
|
|
1044
|
+
expect(r.success).toBe(true);
|
|
1045
|
+
if (r.success) expect((r.data as Record<string, unknown>).entrySource).toBeUndefined();
|
|
1046
|
+
});
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
describe('entrySourceSchema material/template preselects (NFCARD-393)', () => {
|
|
1050
|
+
const base = {
|
|
1051
|
+
sessionId: '550e8400-e29b-41d4-a716-446655440000',
|
|
1052
|
+
userData: validUserData,
|
|
1053
|
+
physicalConfig: validPhysicalConfig,
|
|
1054
|
+
digitalConfig: validDigitalConfig,
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1057
|
+
it('accepts the deep-link preselects', () => {
|
|
1058
|
+
const r = configurationCreateSchema.safeParse({
|
|
1059
|
+
...base,
|
|
1060
|
+
entrySource: { output: 'with-card', material: '3dprint_standard', template: 'tpl-pvc-white' },
|
|
1061
|
+
});
|
|
1062
|
+
expect(r.success).toBe(true);
|
|
1063
|
+
if (r.success) {
|
|
1064
|
+
expect(r.data.entrySource?.material).toBe('3dprint_standard');
|
|
1065
|
+
expect(r.data.entrySource?.template).toBe('tpl-pvc-white');
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
it('rejects a non-enum material', () => {
|
|
1070
|
+
expect(
|
|
1071
|
+
configurationCreateSchema.safeParse({
|
|
1072
|
+
...base,
|
|
1073
|
+
entrySource: { material: 'carbon-fiber' },
|
|
1074
|
+
}).success,
|
|
1075
|
+
).toBe(false);
|
|
1076
|
+
});
|
|
1077
|
+
});
|
package/src/index.ts
CHANGED
|
Binary file
|