-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathimportDryRun.test.ts
More file actions
167 lines (145 loc) · 7.01 KB
/
Copy pathimportDryRun.test.ts
File metadata and controls
167 lines (145 loc) · 7.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import { describe, it, expect } from 'vitest';
import { __testables } from './ImportWizard';
const { assembleImportRequest, formatDryRunError } = __testables;
const rows = [{ name: 'Acme' }, { name: 'Beta' }];
const baseOpts = {
writeMode: 'insert' as const,
matchFields: [] as string[],
createMissingOptions: false,
runAutomations: false,
skipBlankMatchKey: false,
};
describe('assembleImportRequest', () => {
it('omits dryRun on a real import request', () => {
const req = assembleImportRequest(rows, baseOpts);
expect(req).not.toHaveProperty('dryRun');
expect(req.format).toBe('json');
expect(req.rows).toBe(rows);
expect(req.writeMode).toBe('insert');
});
it('sets dryRun:true when validating, keeping the rest of the payload identical', () => {
const live = assembleImportRequest(rows, baseOpts);
const dry = assembleImportRequest(rows, { ...baseOpts, dryRun: true });
expect(dry.dryRun).toBe(true);
// dryRun is the ONLY difference — the pre-check validates the exact payload.
expect({ ...dry, dryRun: undefined }).toEqual({ ...live, dryRun: undefined });
});
it('drops matchFields for insert mode but sends them for update/upsert', () => {
const insert = assembleImportRequest(rows, { ...baseOpts, writeMode: 'insert', matchFields: ['name'] });
expect(insert).not.toHaveProperty('matchFields');
const upsert = assembleImportRequest(rows, { ...baseOpts, writeMode: 'upsert', matchFields: ['name'] });
expect(upsert.matchFields).toEqual(['name']);
const update = assembleImportRequest(rows, { ...baseOpts, writeMode: 'update', matchFields: ['email'] });
expect(update.matchFields).toEqual(['email']);
});
it('threads coercion options through verbatim', () => {
const req = assembleImportRequest(rows, {
...baseOpts,
createMissingOptions: true,
runAutomations: true,
skipBlankMatchKey: true,
});
expect(req.createMissingOptions).toBe(true);
expect(req.runAutomations).toBe(true);
expect(req.skipBlankMatchKey).toBe(true);
});
// framework #3479 — the "historical import" toggle. Sent only when on, so a
// normal import carries no flag and the server keeps enforcing the FSM.
it('sends treatAsHistorical:true only when the option is on', () => {
const on = assembleImportRequest(rows, { ...baseOpts, treatAsHistorical: true });
expect(on.treatAsHistorical).toBe(true);
const off = assembleImportRequest(rows, { ...baseOpts, treatAsHistorical: false });
expect(off).not.toHaveProperty('treatAsHistorical');
const omitted = assembleImportRequest(rows, baseOpts);
expect(omitted).not.toHaveProperty('treatAsHistorical');
});
it('sends treatAsHistorical through the named-mapping path too', () => {
const req = assembleImportRequest(rows, { ...baseOpts, mappingName: 'my-map', treatAsHistorical: true });
expect(req.mappingName).toBe('my-map');
expect(req.treatAsHistorical).toBe(true);
});
});
describe('formatDryRunError', () => {
const labels = new Map([['product', '产品']]);
// Echoes the server's structured English messages for reference failures.
const t = (key: string, vars?: Record<string, unknown>) =>
({
'grid.import.referenceNotFound': `No matching record for "${vars?.value}"`,
'grid.import.referenceAmbiguous': `"${vars?.value}" matches more than one record`,
'grid.import.invalidNumber': `"${vars?.value}" is not a valid number`,
'grid.import.invalidOption': `"${vars?.value}" is not one of the allowed options`,
'grid.import.requiredValue': 'This field is required',
'grid.import.matchAmbiguous': 'Matches more than one existing record — use a unique value or the record id',
} as Record<string, string>)[key] ?? key;
it('resolves the field api-name to its label', () => {
const { fieldLabel } = formatDryRunError(
{ field: 'product', code: 'reference_not_found', error: 'product: no os_x_product matches "导管架"' },
labels, '导管架', t,
);
expect(fieldLabel).toBe('产品');
});
it('renders reference_not_found from the code, not the raw English server text', () => {
const { message } = formatDryRunError(
{ field: 'product', code: 'reference_not_found', error: 'product: no os_x_product matches "导管架"' },
labels, '导管架', t,
);
// No duplicated field name, no internal object api-name leaking through.
expect(message).toBe('No matching record for "导管架"');
expect(message).not.toContain('os_x_product');
expect(message).not.toContain('product:');
});
it('falls back to the quoted value when the cell value is unavailable', () => {
const { message } = formatDryRunError(
{ field: 'product', code: 'reference_not_found', error: 'product: no os_x_product matches "导管架"' },
labels, undefined, t,
);
expect(message).toBe('No matching record for "导管架"');
});
it('maps reference_ambiguous through its own key', () => {
const { message } = formatDryRunError(
{ field: 'product', code: 'reference_ambiguous', error: 'product: "导管架" matches more than one os_x_product' },
labels, '导管架', t,
);
expect(message).toBe('"导管架" matches more than one record');
});
it('maps a coercion code (invalid_number) off the code, not the raw text', () => {
const { message } = formatDryRunError(
{ field: 'product', code: 'invalid_number', error: 'product: "abc" is not a number' },
labels, 'abc', t,
);
expect(message).toBe('"abc" is not a valid number');
expect(message).not.toContain('product:');
});
it('maps invalid_option through its key', () => {
const { message } = formatDryRunError(
{ field: 'product', code: 'invalid_option', error: 'product: "x" is not a known option' },
labels, 'x', t,
);
expect(message).toBe('"x" is not one of the allowed options');
});
it('maps a required-field miss to a value-free message', () => {
const { fieldLabel, message } = formatDryRunError(
{ field: 'product', code: 'required', error: 'product is required' },
labels, undefined, t,
);
expect(fieldLabel).toBe('产品');
expect(message).toBe('This field is required');
});
it('maps AMBIGUOUS_MATCH (upsert match key) without leaking the object api-name', () => {
const { message } = formatDryRunError(
{ field: '', code: 'AMBIGUOUS_MATCH', error: 'matchFields matched more than one os_x_position record' },
labels, undefined, t,
);
expect(message).toBe('Matches more than one existing record — use a unique value or the record id');
expect(message).not.toContain('os_x_position');
});
it('strips a duplicated api-name prefix for codes it does not recognize', () => {
const { fieldLabel, message } = formatDryRunError(
{ field: 'product', code: 'some_other_error', error: 'product: value is out of range' },
labels, 'x', t,
);
expect(fieldLabel).toBe('产品');
// The label carries the field; the message must not repeat "product:".
expect(message).toBe('value is out of range');
});
});