@7365admin1/layer-common 3.2.8-staging.210 → 3.3.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/CHANGELOG.md +537 -0
- package/components/HidQrCodeConfiguration.vue +1 -1
- package/components/HidReaderManagement.vue +1 -0
- package/components/HidUserEnrollment.vue +1 -1
- package/composables/useHidAmico.ts +5 -12
- package/composables/usePromoCode.ts +0 -57
- package/package.json +1 -1
- package/utils/hid-discover.test.ts +2 -15
- package/utils/promo-code-form.test.ts +0 -437
- package/utils/promo-code-form.ts +0 -246
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@7365admin1/layer-common",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.3.0",
|
|
6
6
|
"author": "7365admin1",
|
|
7
7
|
"main": "./nuxt.config.ts",
|
|
8
8
|
"//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",
|
|
@@ -96,20 +96,7 @@ test("the reader form takes site as a prop and puts it in the discover call", ()
|
|
|
96
96
|
assert.match(source, /discoverReader\(\{[\s\S]*?site:\s*props\.site[\s\S]*?\}\)/);
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
-
test("reader management passes its site prop down to the form
|
|
99
|
+
test("reader management passes its site prop down to the form", () => {
|
|
100
100
|
const source = readFileSync(join(root, "components", "HidReaderManagement.vue"), "utf8");
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
// Bound to `props.site`, which is the only name for it that exists in this
|
|
104
|
-
// component - a bare `site` resolves to nothing.
|
|
105
|
-
assert.match(tag, /:site="props\.site"/);
|
|
106
|
-
|
|
107
|
-
/*
|
|
108
|
-
* And exactly once. It was written twice - `props.site` and then a bare
|
|
109
|
-
* `site` - and Vite's Vue plugin treats a duplicate attribute as a COMPILE
|
|
110
|
-
* ERROR, so every consuming application's `nuxt build` failed on this file.
|
|
111
|
-
* The layer's own playground build does not import this component, so
|
|
112
|
-
* nothing here caught it; the first sign was a consumer failing to build.
|
|
113
|
-
*/
|
|
114
|
-
assert.equal((tag.match(/:site=/g) ?? []).length, 1, tag);
|
|
101
|
+
assert.match(source, /<HidReaderForm[\s\S]*?:site="site"[\s\S]*?\/>/);
|
|
115
102
|
});
|
|
@@ -1,437 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { test } from "node:test";
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
promoExpiryDate,
|
|
6
|
-
promoCodeFormValues,
|
|
7
|
-
promoCodeUpdatePayload,
|
|
8
|
-
validatePromoCodeEdit,
|
|
9
|
-
promoCodeApiError,
|
|
10
|
-
} from "./promo-code-form.ts";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* THE PROMO CODE EDITOR AND THE THREE WRITE CALLS BEHIND IT.
|
|
14
|
-
*
|
|
15
|
-
* Two halves, both pinned here:
|
|
16
|
-
*
|
|
17
|
-
* 1. the rules the edit form applies before it sends anything - taken from
|
|
18
|
-
* the MERGED backend (`promoCodeUpdateSchema` in `@7365admin1/core`), not
|
|
19
|
-
* from a guess about it;
|
|
20
|
-
* 2. what `usePromoCode()` actually puts on the wire. The composable is three
|
|
21
|
-
* `$api` calls, and the thing that can silently be wrong about it is the
|
|
22
|
-
* verb, the path or the body - a `PUT` to the wrong path, or a body that
|
|
23
|
-
* carries `code` and so looks as though it renamed a code it cannot.
|
|
24
|
-
*
|
|
25
|
-
* The composable reaches for `useNuxtApp()`, which only exists inside a Nuxt
|
|
26
|
-
* app, so one stub stands in for it and records the call.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
const NOW = new Date(2026, 7, 20, 12, 0, 0); // 20 Aug 2026, midday
|
|
30
|
-
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// The expiry rule
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
|
|
35
|
-
test("an expiry date is read as the END of its day, in both stored formats", () => {
|
|
36
|
-
// A code dated today is good for all of today, so a check at midday passes.
|
|
37
|
-
const usFormat = promoExpiryDate("08/20/2026");
|
|
38
|
-
assert.ok(usFormat);
|
|
39
|
-
assert.equal(usFormat!.getHours(), 23);
|
|
40
|
-
assert.ok(usFormat!.getTime() > NOW.getTime());
|
|
41
|
-
|
|
42
|
-
const iso = promoExpiryDate("2026-08-20");
|
|
43
|
-
assert.ok(iso);
|
|
44
|
-
assert.equal(iso!.getTime(), usFormat!.getTime());
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("a date that is not a date is no expiry at all, never an Invalid Date", () => {
|
|
48
|
-
assert.equal(promoExpiryDate(""), null);
|
|
49
|
-
assert.equal(promoExpiryDate(null), null);
|
|
50
|
-
assert.equal(promoExpiryDate("whenever"), null);
|
|
51
|
-
// Month 13 rolls into the next year in JavaScript rather than failing, so
|
|
52
|
-
// it is caught explicitly - otherwise 13/01/2026 would read as Jan 2027.
|
|
53
|
-
assert.equal(promoExpiryDate("13/01/2026"), null);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// ---------------------------------------------------------------------------
|
|
57
|
-
// Loading a record into the form, and building the body back out
|
|
58
|
-
// ---------------------------------------------------------------------------
|
|
59
|
-
|
|
60
|
-
test("the form loads from the record and does NOT carry the code", () => {
|
|
61
|
-
const values = promoCodeFormValues({
|
|
62
|
-
_id: "652f1a2b3c4d5e6f70819293",
|
|
63
|
-
code: "WELCOME2026",
|
|
64
|
-
description: "Launch offer",
|
|
65
|
-
type: "fixed",
|
|
66
|
-
fixed_rate: 12,
|
|
67
|
-
expiresAt: "12/31/2026",
|
|
68
|
-
status: "active",
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
assert.equal((values as any).code, undefined);
|
|
72
|
-
assert.deepEqual(values, {
|
|
73
|
-
description: "Launch offer",
|
|
74
|
-
type: "fixed",
|
|
75
|
-
fixed_rate: 12,
|
|
76
|
-
expiresAt: "12/31/2026",
|
|
77
|
-
tiers: [],
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test("a tiered record keeps its bands; a fixed one is given none", () => {
|
|
82
|
-
const tiered = promoCodeFormValues({
|
|
83
|
-
type: "tiered",
|
|
84
|
-
tiers: [{ min: 1, max: 10, price: 9 }, { min: 11, max: 0, price: 7 }],
|
|
85
|
-
});
|
|
86
|
-
assert.equal(tiered.tiers.length, 2);
|
|
87
|
-
assert.deepEqual(tiered.tiers[1], { min: 11, max: 0, price: 7 });
|
|
88
|
-
|
|
89
|
-
// A code stored as fixed but carrying leftover bands must not send them:
|
|
90
|
-
// `promoCodeUpdateSchema` FORBIDS `tiers` on a fixed code and would 400.
|
|
91
|
-
const fixed = promoCodeFormValues({
|
|
92
|
-
type: "fixed",
|
|
93
|
-
fixed_rate: 5,
|
|
94
|
-
tiers: [{ min: 1, max: 10, price: 9 }],
|
|
95
|
-
});
|
|
96
|
-
assert.deepEqual(fixed.tiers, []);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("an empty record gives a usable, valid, fixed-price form", () => {
|
|
100
|
-
const values = promoCodeFormValues(null);
|
|
101
|
-
assert.deepEqual(values, {
|
|
102
|
-
description: "",
|
|
103
|
-
type: "fixed",
|
|
104
|
-
fixed_rate: 0,
|
|
105
|
-
expiresAt: "",
|
|
106
|
-
tiers: [],
|
|
107
|
-
});
|
|
108
|
-
assert.equal(validatePromoCodeEdit(promoCodeUpdatePayload(values), NOW), null);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("the payload never contains code, _id or status", () => {
|
|
112
|
-
const payload = promoCodeUpdatePayload({
|
|
113
|
-
description: "Launch offer",
|
|
114
|
-
type: "fixed",
|
|
115
|
-
fixed_rate: 12,
|
|
116
|
-
expiresAt: "12/31/2026",
|
|
117
|
-
tiers: [],
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
assert.deepEqual(Object.keys(payload).sort(), [
|
|
121
|
-
"description",
|
|
122
|
-
"expiresAt",
|
|
123
|
-
"fixed_rate",
|
|
124
|
-
"type",
|
|
125
|
-
]);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test("a tiered payload sends bands and no fixed_rate, and the reverse", () => {
|
|
129
|
-
const tiered = promoCodeUpdatePayload({
|
|
130
|
-
description: "",
|
|
131
|
-
type: "tiered",
|
|
132
|
-
fixed_rate: 99,
|
|
133
|
-
expiresAt: "",
|
|
134
|
-
tiers: [{ min: 1, max: 10, price: 9 }],
|
|
135
|
-
});
|
|
136
|
-
assert.deepEqual(tiered.tiers, [{ min: 1, max: 10, price: 9 }]);
|
|
137
|
-
assert.equal("fixed_rate" in tiered, false);
|
|
138
|
-
|
|
139
|
-
const fixed = promoCodeUpdatePayload({
|
|
140
|
-
description: "",
|
|
141
|
-
type: "fixed",
|
|
142
|
-
fixed_rate: 9,
|
|
143
|
-
expiresAt: "",
|
|
144
|
-
tiers: [{ min: 1, max: 10, price: 9 }],
|
|
145
|
-
});
|
|
146
|
-
assert.equal("tiers" in fixed, false);
|
|
147
|
-
assert.equal(fixed.fixed_rate, 9);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
test("a price nobody has typed is NOT quietly saved as free", () => {
|
|
151
|
-
// `Number("")` is 0. Coercing a blank field would turn "not filled in" into
|
|
152
|
-
// "every seat is free" - a real price, saved without anyone choosing it.
|
|
153
|
-
const payload = promoCodeUpdatePayload({
|
|
154
|
-
description: "",
|
|
155
|
-
type: "fixed",
|
|
156
|
-
fixed_rate: "" as any,
|
|
157
|
-
expiresAt: "",
|
|
158
|
-
tiers: [],
|
|
159
|
-
});
|
|
160
|
-
assert.equal(payload.fixed_rate, "");
|
|
161
|
-
assert.match(
|
|
162
|
-
String(validatePromoCodeEdit(payload, NOW)),
|
|
163
|
-
/Enter a price per seat/
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
// A deliberate 0, however, is a real answer and goes through.
|
|
167
|
-
const free = promoCodeUpdatePayload({
|
|
168
|
-
description: "",
|
|
169
|
-
type: "fixed",
|
|
170
|
-
fixed_rate: 0,
|
|
171
|
-
expiresAt: "",
|
|
172
|
-
tiers: [],
|
|
173
|
-
});
|
|
174
|
-
assert.equal(free.fixed_rate, 0);
|
|
175
|
-
assert.equal(validatePromoCodeEdit(free, NOW), null);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
test("band numbers typed as text reach the API as numbers", () => {
|
|
179
|
-
const payload = promoCodeUpdatePayload({
|
|
180
|
-
description: "",
|
|
181
|
-
type: "tiered",
|
|
182
|
-
fixed_rate: 0,
|
|
183
|
-
expiresAt: "",
|
|
184
|
-
tiers: [{ min: "1", max: "10", price: "9.5" } as any],
|
|
185
|
-
});
|
|
186
|
-
assert.deepEqual(payload.tiers, [{ min: 1, max: 10, price: 9.5 }]);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
// ---------------------------------------------------------------------------
|
|
190
|
-
// What the form refuses, in the API's terms
|
|
191
|
-
// ---------------------------------------------------------------------------
|
|
192
|
-
|
|
193
|
-
test("a valid edit is refused nothing", () => {
|
|
194
|
-
assert.equal(
|
|
195
|
-
validatePromoCodeEdit(
|
|
196
|
-
{ type: "fixed", fixed_rate: 0, expiresAt: "", description: "" },
|
|
197
|
-
NOW
|
|
198
|
-
),
|
|
199
|
-
null
|
|
200
|
-
);
|
|
201
|
-
assert.equal(
|
|
202
|
-
validatePromoCodeEdit(
|
|
203
|
-
{
|
|
204
|
-
type: "tiered",
|
|
205
|
-
tiers: [{ min: 1, max: 10, price: 9 }, { min: 11, max: 0, price: 7 }],
|
|
206
|
-
expiresAt: "12/31/2026",
|
|
207
|
-
},
|
|
208
|
-
NOW
|
|
209
|
-
),
|
|
210
|
-
null
|
|
211
|
-
);
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
test("an expiry already in the past is refused, and says what to do", () => {
|
|
215
|
-
const message = validatePromoCodeEdit(
|
|
216
|
-
{ type: "fixed", fixed_rate: 5, expiresAt: "01/01/2020" },
|
|
217
|
-
NOW
|
|
218
|
-
);
|
|
219
|
-
assert.match(String(message), /already passed/);
|
|
220
|
-
assert.match(String(message), /clear the date/);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
test("TODAY is not in the past - a code expiring today is still savable", () => {
|
|
224
|
-
assert.equal(
|
|
225
|
-
validatePromoCodeEdit(
|
|
226
|
-
{ type: "fixed", fixed_rate: 5, expiresAt: "08/20/2026" },
|
|
227
|
-
NOW
|
|
228
|
-
),
|
|
229
|
-
null
|
|
230
|
-
);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
test("no expiry at all is allowed - that is a code with no end date", () => {
|
|
234
|
-
assert.equal(
|
|
235
|
-
validatePromoCodeEdit({ type: "fixed", fixed_rate: 5, expiresAt: "" }, NOW),
|
|
236
|
-
null
|
|
237
|
-
);
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
test("an unreadable expiry names the format instead of guessing", () => {
|
|
241
|
-
assert.match(
|
|
242
|
-
String(
|
|
243
|
-
validatePromoCodeEdit(
|
|
244
|
-
{ type: "fixed", fixed_rate: 5, expiresAt: "next Tuesday" },
|
|
245
|
-
NOW
|
|
246
|
-
)
|
|
247
|
-
),
|
|
248
|
-
/MM\/DD\/YYYY/
|
|
249
|
-
);
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
test("a free BAND is refused, because Joi's positive() refuses it", () => {
|
|
253
|
-
const message = validatePromoCodeEdit(
|
|
254
|
-
{ type: "tiered", tiers: [{ min: 1, max: 10, price: 0 }] },
|
|
255
|
-
NOW
|
|
256
|
-
);
|
|
257
|
-
assert.match(String(message), /^Band 1: /);
|
|
258
|
-
assert.match(String(message), /above 0/);
|
|
259
|
-
// ...and it says the way to actually make seats free, which is the other
|
|
260
|
-
// pricing shape. `fixed_rate` is `min(0)`, not `positive()`.
|
|
261
|
-
assert.equal(
|
|
262
|
-
validatePromoCodeEdit({ type: "fixed", fixed_rate: 0 }, NOW),
|
|
263
|
-
null
|
|
264
|
-
);
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
test("the refused band is named, not just 'a band'", () => {
|
|
268
|
-
assert.match(
|
|
269
|
-
String(
|
|
270
|
-
validatePromoCodeEdit(
|
|
271
|
-
{
|
|
272
|
-
type: "tiered",
|
|
273
|
-
tiers: [
|
|
274
|
-
{ min: 1, max: 10, price: 9 },
|
|
275
|
-
{ min: 11, max: 5, price: 7 },
|
|
276
|
-
],
|
|
277
|
-
},
|
|
278
|
-
NOW
|
|
279
|
-
)
|
|
280
|
-
),
|
|
281
|
-
/^Band 2: /
|
|
282
|
-
);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
test("a band with no upper limit is allowed - 0 means 'and above'", () => {
|
|
286
|
-
assert.equal(
|
|
287
|
-
validatePromoCodeEdit(
|
|
288
|
-
{ type: "tiered", tiers: [{ min: 5, max: 0, price: 7 }] },
|
|
289
|
-
NOW
|
|
290
|
-
),
|
|
291
|
-
null
|
|
292
|
-
);
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
test("a tiered code with no bands is refused, and offers the other shape", () => {
|
|
296
|
-
assert.match(
|
|
297
|
-
String(validatePromoCodeEdit({ type: "tiered", tiers: [] }, NOW)),
|
|
298
|
-
/at least one quantity band/
|
|
299
|
-
);
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
test("a negative fixed price is refused; a missing one is refused too", () => {
|
|
303
|
-
assert.match(
|
|
304
|
-
String(validatePromoCodeEdit({ type: "fixed", fixed_rate: -1 }, NOW)),
|
|
305
|
-
/cannot be less than 0/
|
|
306
|
-
);
|
|
307
|
-
assert.match(
|
|
308
|
-
String(validatePromoCodeEdit({ type: "fixed", fixed_rate: "" }, NOW)),
|
|
309
|
-
/Enter a price per seat/
|
|
310
|
-
);
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
test("a code with no pricing shape is refused first, before anything else", () => {
|
|
314
|
-
assert.equal(
|
|
315
|
-
validatePromoCodeEdit({ expiresAt: "01/01/2020" }, NOW),
|
|
316
|
-
"Choose how this code prices seats."
|
|
317
|
-
);
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
// ---------------------------------------------------------------------------
|
|
321
|
-
// What a refusal from the server reads like
|
|
322
|
-
// ---------------------------------------------------------------------------
|
|
323
|
-
|
|
324
|
-
test("the API's own wording is what the screen shows", () => {
|
|
325
|
-
assert.equal(
|
|
326
|
-
promoCodeApiError(
|
|
327
|
-
{ response: { _data: { message: "Promo code not found." } } },
|
|
328
|
-
"Could not save."
|
|
329
|
-
),
|
|
330
|
-
"Promo code not found."
|
|
331
|
-
);
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
test("'Not authorized.' is replaced with something a person can act on", () => {
|
|
335
|
-
const message = promoCodeApiError(
|
|
336
|
-
{ data: { message: "Not authorized." } },
|
|
337
|
-
"Could not save."
|
|
338
|
-
);
|
|
339
|
-
assert.match(message, /Seven365 staff account/);
|
|
340
|
-
assert.match(message, /Ask a Seven365 administrator/);
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
test("an unreadable failure falls back rather than dumping the error", () => {
|
|
344
|
-
assert.equal(
|
|
345
|
-
promoCodeApiError(new Error("fetch failed"), "Could not save the changes."),
|
|
346
|
-
"Could not save the changes."
|
|
347
|
-
);
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
// ---------------------------------------------------------------------------
|
|
351
|
-
// The composable: verb, path and body of every write
|
|
352
|
-
// ---------------------------------------------------------------------------
|
|
353
|
-
|
|
354
|
-
const calls: Array<{ path: string; options: any }> = [];
|
|
355
|
-
|
|
356
|
-
(globalThis as any).useNuxtApp = () => ({
|
|
357
|
-
$api: (path: string, options: any = {}) => {
|
|
358
|
-
calls.push({ path, options });
|
|
359
|
-
return Promise.resolve({ message: "ok" });
|
|
360
|
-
},
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
const promoCodeApi = (await import("../composables/usePromoCode.ts")).default();
|
|
364
|
-
|
|
365
|
-
function lastCall() {
|
|
366
|
-
return calls[calls.length - 1];
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const ID = "652f1a2b3c4d5e6f70819293";
|
|
370
|
-
|
|
371
|
-
test("getById reads the one record, from the path the API mounts", async () => {
|
|
372
|
-
await promoCodeApi.getById(ID);
|
|
373
|
-
assert.equal(lastCall().path, `/api/promo-codes/id/${ID}`);
|
|
374
|
-
assert.equal(lastCall().options.method, "GET");
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
test("update is a PUT to /api/promo-codes/:id", async () => {
|
|
378
|
-
await promoCodeApi.update(ID, {
|
|
379
|
-
description: "Launch offer",
|
|
380
|
-
type: "fixed",
|
|
381
|
-
fixed_rate: 12,
|
|
382
|
-
expiresAt: "12/31/2026",
|
|
383
|
-
} as any);
|
|
384
|
-
|
|
385
|
-
assert.equal(lastCall().path, `/api/promo-codes/${ID}`);
|
|
386
|
-
assert.equal(lastCall().options.method, "PUT");
|
|
387
|
-
assert.equal(lastCall().options.body.description, "Launch offer");
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
test("update NEVER sends code, however it is called", async () => {
|
|
391
|
-
// The screen loads a record and could easily send it back whole. `code` is
|
|
392
|
-
// immutable on the server; sending it would be accepted and dropped, and
|
|
393
|
-
// the person would watch a rename succeed and change nothing.
|
|
394
|
-
await promoCodeApi.update(ID, {
|
|
395
|
-
code: "RENAMED",
|
|
396
|
-
type: "fixed",
|
|
397
|
-
fixed_rate: 1,
|
|
398
|
-
} as any);
|
|
399
|
-
|
|
400
|
-
assert.equal("code" in lastCall().options.body, false);
|
|
401
|
-
assert.equal(lastCall().options.body.type, "fixed");
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
test("updateStatus is a PATCH carrying only the new status", async () => {
|
|
405
|
-
await promoCodeApi.updateStatus(ID, "disabled");
|
|
406
|
-
assert.equal(lastCall().path, `/api/promo-codes/${ID}/status`);
|
|
407
|
-
assert.equal(lastCall().options.method, "PATCH");
|
|
408
|
-
assert.deepEqual(lastCall().options.body, { status: "disabled" });
|
|
409
|
-
|
|
410
|
-
await promoCodeApi.updateStatus(ID, "active");
|
|
411
|
-
assert.deepEqual(lastCall().options.body, { status: "active" });
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
test("remove is a DELETE, with no body to get wrong", async () => {
|
|
415
|
-
await promoCodeApi.remove(ID);
|
|
416
|
-
assert.equal(lastCall().path, `/api/promo-codes/${ID}`);
|
|
417
|
-
assert.equal(lastCall().options.method, "DELETE");
|
|
418
|
-
assert.equal(lastCall().options.body, undefined);
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
test("the four reads and writes are all on the composable", () => {
|
|
422
|
-
for (const name of [
|
|
423
|
-
"add",
|
|
424
|
-
"getPromoCodes",
|
|
425
|
-
"getByCode",
|
|
426
|
-
"getById",
|
|
427
|
-
"update",
|
|
428
|
-
"updateStatus",
|
|
429
|
-
"remove",
|
|
430
|
-
]) {
|
|
431
|
-
assert.equal(
|
|
432
|
-
typeof (promoCodeApi as any)[name],
|
|
433
|
-
"function",
|
|
434
|
-
`usePromoCode() is missing ${name}`
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
});
|