@schwabyio/ata 0.0.0-stage → 0.20.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.
@@ -0,0 +1,594 @@
1
+ # The `ata` functions
2
+
3
+ Code in a collection runs in two places: `before.script`, before a request is sent, and
4
+ `tests`, after its response arrives. Both have an `ata` object, with nothing to import or
5
+ load. This document lists every function on it.
6
+
7
+ ```yaml
8
+ - name: get user
9
+ GET: '{{baseUrl}}/users/7'
10
+ before:
11
+ script: |
12
+ ata.set('traceId', ata.uuidv7())
13
+ tests: |
14
+ ata.expectResponseStatusCodeToBe(200)
15
+ ata.expectResponseBodyToHaveProperty('user.name', 'Ada')
16
+ ata.expectResponseBodyToHaveProperty('user.email', 'userEmail', 'setAsCollectionVariable')
17
+ ```
18
+
19
+ [SPEC.md](./SPEC.md) specifies the files this code lives in. §5 there covers where code
20
+ goes, the order it runs in, and the other globals: `res`, `req`, `assert`, `params`,
21
+ `endpoint` and `checks`.
22
+
23
+ ---
24
+
25
+ ## At a glance
26
+
27
+ | Function | What it does | `tests` | `before.script` |
28
+ | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------- | :-----: | :-------------: |
29
+ | [`expectResponseStatusCodeToBe`](#ataexpectresponsestatuscodetobe) | Check the status code | ✓ | |
30
+ | [`expectResponseToHaveHeader`](#ataexpectresponsetohaveheader) | Check a header | ✓ | |
31
+ | [`expectResponseToHaveHeaderCount`](#ataexpectresponsetohaveheadercount) | Check how many times a header comes | ✓ | |
32
+ | [`expectResponseBodyToHaveProperty`](#ataexpectresponsebodytohaveproperty) | Check a property of the body | ✓ | |
33
+ | [`expectResponseBodyToHaveUnorderedArray`](#ataexpectresponsebodytohaveunorderedarray) | Check an array holds items, in any order | ✓ | |
34
+ | [`expectResponseBodyToHaveUnorderedArrayNotThisItem`](#ataexpectresponsebodytohaveunorderedarraynotthisitem) | Check an array holds none of some items | ✓ | |
35
+ | [`sortResponseBodyArrays`](#atasortresponsebodyarrays) | Sort arrays before the checks after it | ✓ | |
36
+ | [`useStrictValidation`](#atausestrictvalidation) | Fail when any body property goes unchecked | ✓ | |
37
+ | [`ignoreResponseBodyProperty`](#ataignoreresponsebodyproperty) | Leave a property out of strict validation | ✓ | |
38
+ | [`ignoreResponseBodyArrayObjectProperty`](#ataignoreresponsebodyarrayobjectproperty) | The same, for a property of every item of an array | ✓ | |
39
+ | [`test`](#atatest) | A named check of your own | ✓ | |
40
+ | [`get`](#ataget) | Read a variable | ✓ | ✓ |
41
+ | [`set`](#ataset) | Set a variable for the steps after | ✓ | ✓ |
42
+ | [`skip`](#ataskip) | Send nothing for this step | | ✓ |
43
+ | [`skipRest`](#ataskiprest) | Skip the steps after this one | ✓ | ✓ |
44
+ | [`flag`](#ataflag) | Read a feature flag | ✓ | ✓ |
45
+ | [`uuid`](#atauuid) | A random UUID | ✓ | ✓ |
46
+ | [`uuidv7`](#atauuidv7) | A time-ordered UUID | ✓ | ✓ |
47
+ | [`randomInt`](#atarandomint) | A random whole number | ✓ | ✓ |
48
+ | [`date`](#atadate) | A formatted date, now or offset from now | ✓ | ✓ |
49
+
50
+ ## How calls behave
51
+
52
+ - **Every `tests` script of a step feeds one list of checks**: the collection's, the
53
+ step's, and those of an endpoint base, a base collection or reusable requests it runs
54
+ under (SPEC.md §2.5–§2.7). Strict validation counts them all together.
55
+ - **A check that fails does not stop the script.** The checks after it still run, and
56
+ the step fails.
57
+ - **A mistake in how a check is called is a failed check** that says what is wrong: an
58
+ unknown `specialHandling`, a length that is not a number, a path that is neither text
59
+ nor a list. The checks after it still run.
60
+ - **Any other error stops the script**: a misspelled function name, a call that belongs
61
+ in the other script, or a JavaScript error. The step is marked errored, with the line,
62
+ and checks made before it are kept.
63
+ - **A script is stopped after 10 seconds.**
64
+ - **Check files** in `checks/` call `ata` too, and what they check is reported on the
65
+ step that called them (SPEC.md §5).
66
+
67
+ ---
68
+
69
+ ## Checking the response
70
+
71
+ These run in `tests`. Calling one in `before.script` is an error, since nothing has been
72
+ received yet.
73
+
74
+ ### Paths
75
+
76
+ A path finds a property in the body:
77
+
78
+ | Path | Finds |
79
+ | --------------------------------------- | --------------------------------------------------------------- |
80
+ | `user.name` | A property of a property |
81
+ | `groups.0.name` or `groups[0].name` | A property of an array's first item |
82
+ | `sessions[].id` | The property of every item of an array |
83
+ | `jwt.payload["https://x.io/id"]` | A key holding `.`, `[` or `]`, written in brackets as JSON text |
84
+ | `modules[""].edition` | An empty key |
85
+ | `['jwt', 'payload', 'https://x.io/id']` | A list of keys: each item is one key, whatever it holds |
86
+
87
+ In a list of keys a number is its digits, so `['groups', 0, 'name']` reads an index.
88
+ Every function that takes a path takes a list too, and so does `pathToProperty`.
89
+
90
+ The body is read as checks need it. JSON is used as it is. XML is converted: the root
91
+ element is the one top-level key, an element holding only text becomes that text, and a
92
+ repeated element becomes an array. A text or HTML body is one property, `plaintext`.
93
+ An event stream (`text/event-stream`) is a list with one item per event, holding its
94
+ `data` and, when the event set them, its `event` and `id`: `[1].data.price` is the
95
+ second event's price. SPEC.md §3 has the full rules.
96
+
97
+ ### Values
98
+
99
+ - **A body property compares with its type.** `'12345'` does not equal `12345`, and the
100
+ failure says which is which.
101
+ - **The status and headers compare as text**, so `200` and `'200'` are the same.
102
+ - **A `RegExp` is a pattern**, flags included, tested against the value as text.
103
+
104
+ ### `specialHandling`
105
+
106
+ The last argument of a check may be one of these strings, to change what it asks:
107
+
108
+ | String | Means |
109
+ | -------------------------- | ----------------------------------------------------------------------------------- |
110
+ | _(none, and no value)_ | The property or header is present. |
111
+ | `notThisExpectedKey` | It must not be present. Pass `null` as the value. |
112
+ | `notThisExpectedValue` | It must be present, and not equal the value or match the `RegExp`. |
113
+ | `setAsCollectionVariable` | Save it into the variable the value names, for the steps after. It must be present. |
114
+ | `setAsEnvironmentVariable` | The same. Nothing is written to the environment file. |
115
+ | `dateAsEpoch` | Epoch milliseconds, on the calendar day the value names (below). |
116
+ | `dateWithin<X>Sec` | A date within X seconds of the value, as in `dateWithin5Sec`. |
117
+ | `integerWithin<X>` | A number within X of the value, as in `integerWithin2`. |
118
+ | `isArray` | An array. What it holds is not checked. Pass `null` as the value. |
119
+ | `isArrayAndEmpty` | An empty array. Pass `null` as the value. |
120
+ | `isArrayAndNotEmpty` | An array with at least one item. Pass `null` as the value. |
121
+ | `isArrayAndHasLength` | An array of exactly the value's length. |
122
+
123
+ - The status takes `notThisExpectedValue` and the two `setAs…` strings. A header takes
124
+ those and `notThisExpectedKey`. The rest are for body properties.
125
+ - **`dateAsEpoch`**: the property holds epoch milliseconds, as a number or as digits.
126
+ The value is a number of seconds from now (`0` is today, `86400` tomorrow), or a date
127
+ whose first ten characters, `2026-10-01`, must match. Days are the calendar days of
128
+ the machine running the test.
129
+ - **`dateWithin<X>Sec`**: the property and the value are each an ISO 8601 date or epoch
130
+ milliseconds.
131
+ - **Saving** keeps a string, number, boolean or null as it is. An object or array is
132
+ saved as its JSON text.
133
+
134
+ ### `ata.expectResponseStatusCodeToBe`
135
+
136
+ ```js
137
+ ata.expectResponseStatusCodeToBe(expected, specialHandling?)
138
+ ```
139
+
140
+ The status code is `expected`, or matches it.
141
+
142
+ ```js
143
+ ata.expectResponseStatusCodeToBe(201)
144
+ ata.expectResponseStatusCodeToBe(/^2\d\d$/) // any 2xx
145
+ ata.expectResponseStatusCodeToBe(500, 'notThisExpectedValue') // anything but 500
146
+ ata.expectResponseStatusCodeToBe('lastStatus', 'setAsCollectionVariable')
147
+ ```
148
+
149
+ ### `ata.expectResponseToHaveHeader`
150
+
151
+ ```js
152
+ ata.expectResponseToHaveHeader(name, expected?, specialHandling?)
153
+ ```
154
+
155
+ The header is present, and with `expected`, equals it or matches it.
156
+
157
+ ```js
158
+ ata.expectResponseToHaveHeader('X-Request-Id')
159
+ ata.expectResponseToHaveHeader('Content-Type', /^application\/json/)
160
+ ata.expectResponseToHaveHeader('Cache-Control', 'no-store')
161
+ ata.expectResponseToHaveHeader('X-Debug', null, 'notThisExpectedKey')
162
+ ata.expectResponseToHaveHeader('Server', /nginx/, 'notThisExpectedValue')
163
+ ata.expectResponseToHaveHeader('X-Request-Id', 'requestId', 'setAsCollectionVariable')
164
+ ```
165
+
166
+ - The name matches in any case.
167
+ - A header sent more than once is its values joined with `, `, as in `a=1, b=2`. To
168
+ check how many times it came, use
169
+ [`expectResponseToHaveHeaderCount`](#ataexpectresponsetohaveheadercount). To check one
170
+ value among several, read them as a list with `res.headerValues(name)`:
171
+
172
+ ```js
173
+ ata.test('a session cookie', () =>
174
+ assert.ok(res.headerValues('Set-Cookie').some((cookie) => cookie.startsWith('session=')))
175
+ )
176
+ ```
177
+
178
+ ### `ata.expectResponseToHaveHeaderCount`
179
+
180
+ ```js
181
+ ata.expectResponseToHaveHeaderCount(name, expectedCount)
182
+ ```
183
+
184
+ The response has the header exactly `expectedCount` times.
185
+
186
+ ```js
187
+ ata.expectResponseToHaveHeaderCount('Set-Cookie', 2) // a session cookie and a CSRF cookie
188
+ ata.expectResponseToHaveHeaderCount('X-Request-Id', 1) // exactly one, never repeated
189
+ ata.expectResponseToHaveHeaderCount('X-Debug', 0) // not there at all
190
+ ```
191
+
192
+ - The name matches in any case, so `set-cookie` and `Set-Cookie` count together.
193
+ - `expectedCount` is a whole number, 0 or more. Anything else fails the check and says
194
+ why, and the checks after it still run.
195
+ - In the response's Headers tab, each header of that name is marked with the result.
196
+ - As with any header check, a step's check of a header replaces an endpoint base's check
197
+ of the same header ([SPEC.md §2.6](./SPEC.md#26-endpoint-bases)).
198
+
199
+ ### `ata.expectResponseBodyToHaveProperty`
200
+
201
+ ```js
202
+ ata.expectResponseBodyToHaveProperty(path, expected?, specialHandling?)
203
+ ```
204
+
205
+ The property at [`path`](#paths) is present, and with `expected`, equals it or matches
206
+ it.
207
+
208
+ ```js
209
+ ata.expectResponseBodyToHaveProperty('user.id')
210
+ ata.expectResponseBodyToHaveProperty('user.name', 'Ada')
211
+ ata.expectResponseBodyToHaveProperty('user.email', /@example\.com$/)
212
+ ata.expectResponseBodyToHaveProperty('user.nickname', null, 'notThisExpectedKey')
213
+ ata.expectResponseBodyToHaveProperty('user.score', 100, 'integerWithin2')
214
+ ata.expectResponseBodyToHaveProperty('user.roles', 2, 'isArrayAndHasLength')
215
+ ata.expectResponseBodyToHaveProperty('user.token', 'token', 'setAsCollectionVariable')
216
+ ata.expectResponseBodyToHaveProperty('items[].status', 'active') // every item
217
+ ```
218
+
219
+ - **`expected` is a string, number, boolean, `null` or `RegExp`.** An object or array
220
+ never equals one. Check its properties one by one, use
221
+ [`expectResponseBodyToHaveUnorderedArray`](#ataexpectresponsebodytohaveunorderedarray)
222
+ for an array, or compare it in [`ata.test`](#atatest) with `assert.deepEqual`.
223
+ - **A property whose value is `null` is present.**
224
+ - **A path that runs into a `null` before its end**, such as `phone.number` when `phone`
225
+ is `null`, reads as `null` for a check that the value is `null`. For any other check,
226
+ the property is not present.
227
+ - **A path with `[]` checks every item**, and passes only when each one does. An empty
228
+ array has no items, so the property is not present. Saving one saves every item's
229
+ value, as a JSON list.
230
+
231
+ ### `ata.expectResponseBodyToHaveUnorderedArray`
232
+
233
+ ```js
234
+ ata.expectResponseBodyToHaveUnorderedArray(path, list)
235
+ ```
236
+
237
+ The array at `path` holds what `list` describes, in any order. `list` takes one of two
238
+ forms.
239
+
240
+ **A list of values.** Each must be in the array, which may hold others too. A `RegExp`
241
+ is a pattern, which some item must match:
242
+
243
+ ```js
244
+ ata.expectResponseBodyToHaveUnorderedArray('roles', ['admin', 'editor'])
245
+ ata.expectResponseBodyToHaveUnorderedArray('roles', [/^admin/, 'editor'])
246
+ ata.expectResponseBodyToHaveUnorderedArray('users', [{ name: 'Ada' }, { name: /^Grace/ }])
247
+ ```
248
+
249
+ An object in the list matches an item holding each of its properties with that value, or
250
+ matching it when the value is a `RegExp`. The item may have others. A property that is
251
+ itself an object is matched the same way, so `{ data: { status: 'reversed' } }` finds an
252
+ item whose `data` has that status, whatever else `data` holds. An array compares whole.
253
+ A pattern is tested against the value as text, so it never matches an item that is an
254
+ object or an array.
255
+
256
+ **A list of `{ pathToProperty, expectedValue, specialHandling? }` entries.** Together
257
+ they describe **one** item, property by property, and some item must match every entry.
258
+ Call the function once for each item:
259
+
260
+ ```js
261
+ ata.expectResponseBodyToHaveUnorderedArray('users', [
262
+ { pathToProperty: 'name', expectedValue: 'Ada' },
263
+ { pathToProperty: 'role', expectedValue: /^admin/ },
264
+ { pathToProperty: 'nickname', expectedValue: null, specialHandling: 'notThisExpectedKey' },
265
+ { pathToProperty: 'id', expectedValue: 'adaId', specialHandling: 'setAsCollectionVariable' }
266
+ ])
267
+ ```
268
+
269
+ - `pathToProperty` is a path inside the item. `specialHandling` is any of the strings
270
+ above, and a save takes its value from the item that matched.
271
+ - A property may be named twice: once to check it, and once to save it.
272
+ - **Each call prefers items an earlier call did not match.** Two calls with the same
273
+ description find two items when there are two, so each saves from, and strict
274
+ validation counts, a different one. A sort starts this over.
275
+ - **A list of one `notThisExpectedValue` entry depends on strict validation.** Without
276
+ it, the entry means no item has that value, so an empty array passes. With it, the
277
+ entry means one item whose value is something else, as any list does. The step's last
278
+ call to `useStrictValidation` decides.
279
+
280
+ ### `ata.expectResponseBodyToHaveUnorderedArrayNotThisItem`
281
+
282
+ ```js
283
+ ata.expectResponseBodyToHaveUnorderedArrayNotThisItem(path, list)
284
+ ```
285
+
286
+ No item of the array at `path` matches what `list` describes.
287
+
288
+ ```js
289
+ ata.expectResponseBodyToHaveUnorderedArrayNotThisItem('roles', ['owner', /^guest/])
290
+ ata.expectResponseBodyToHaveUnorderedArrayNotThisItem('users', [
291
+ { pathToProperty: 'status', compareValue: 'deleted' },
292
+ { pathToProperty: 'email', compareValue: /@test\.invalid$/ }
293
+ ])
294
+ ```
295
+
296
+ - A list of values: none of them may be in the array, and no item may match a `RegExp`
297
+ among them. An object in the list may hold patterns too, as above.
298
+ - A list of `{ pathToProperty, compareValue }` entries describes one item. The check
299
+ fails when some item matches every entry. `compareValue` may be a `RegExp`.
300
+ - **The key is `compareValue`, not `expectedValue`.** An entry without it matches no
301
+ item, so the check always passes.
302
+
303
+ ### `ata.sortResponseBodyArrays`
304
+
305
+ ```js
306
+ ata.sortResponseBodyArrays(property)
307
+ ```
308
+
309
+ Sorts every array of objects that holds `property`, anywhere in the body, for the
310
+ checks after it. Use it when an API returns a list in no fixed order and a check names
311
+ an index.
312
+
313
+ ```js
314
+ ata.sortResponseBodyArrays('id')
315
+ ata.expectResponseBodyToHaveProperty('accounts[0].id', 'acct-100')
316
+ ```
317
+
318
+ - `property` may be a path inside each item, such as `id.value`. Arrays inside the items
319
+ are sorted too.
320
+ - Items without the property go last. Values compare alphanumerically, so `Group 2`
321
+ comes before `Group 10`.
322
+ - Checks made before the call see the order as received, and so does `res.body`.
323
+ - Calling it again sorts by the new property first, and by the earlier ones where that
324
+ ties.
325
+ - Called with no property, it sorts nothing and writes a warning to the step's console.
326
+
327
+ ---
328
+
329
+ ## Strict validation
330
+
331
+ ### `ata.useStrictValidation`
332
+
333
+ ```js
334
+ ata.useStrictValidation(enabled?)
335
+ ```
336
+
337
+ Fails the step unless every property of the body is checked, ignored or saved. It
338
+ catches a response that gained a field no test looks at. `enabled` is `true` when left
339
+ out.
340
+
341
+ ```js
342
+ ata.useStrictValidation()
343
+ ata.expectResponseBodyToHaveProperty('id', 'orderId', 'setAsCollectionVariable')
344
+ ata.expectResponseBodyToHaveProperty('status', 'open')
345
+ ata.ignoreResponseBodyProperty('createdAt')
346
+ ```
347
+
348
+ - It adds one check, `Strict: every body property is asserted`, which lists each
349
+ property left over.
350
+ - It is judged after every `tests` script of the step has run, over what any of them
351
+ checked. Put it in the collection's `tests` to cover every step.
352
+ - `null`, `""`, and empty arrays and objects never need a check of their own.
353
+ - A check of a value's content accounts for it and everything inside it. A check of
354
+ shape only (`isArray`, `isArrayAndHasLength`, or that an object is present) does not
355
+ vouch for what is inside.
356
+ - `false` turns it off. The last call wins, so a step can turn off what the collection
357
+ turned on. `'true'` as text counts as `true`, so a variable can decide:
358
+ `ata.useStrictValidation(ata.get('strictValidation'))`.
359
+ - A binary or HTML body has no properties, so strict validation passes.
360
+
361
+ ### `ata.ignoreResponseBodyProperty`
362
+
363
+ ```js
364
+ ata.ignoreResponseBodyProperty(path)
365
+ ```
366
+
367
+ Counts the property at `path`, and everything inside it, as checked, without checking
368
+ it. For values that change on every call, such as timestamps.
369
+
370
+ ```js
371
+ ata.ignoreResponseBodyProperty('meta')
372
+ ata.ignoreResponseBodyProperty('items[].updatedAt')
373
+ ```
374
+
375
+ It changes nothing unless strict validation is on.
376
+
377
+ ### `ata.ignoreResponseBodyArrayObjectProperty`
378
+
379
+ ```js
380
+ ata.ignoreResponseBodyArrayObjectProperty(arrayPath, propertyPath)
381
+ ```
382
+
383
+ The same, for one property of every item of an array.
384
+ `ata.ignoreResponseBodyArrayObjectProperty('items', 'updatedAt')` is
385
+ `ata.ignoreResponseBodyProperty('items[].updatedAt')`.
386
+
387
+ ---
388
+
389
+ ## Checks of your own
390
+
391
+ ### `ata.test`
392
+
393
+ ```js
394
+ ata.test(name, fn)
395
+ ```
396
+
397
+ A named check, for anything the functions above do not cover. It passes unless `fn`
398
+ throws, or the promise it returns rejects, and then the error's message is the reason.
399
+
400
+ ```js
401
+ ata.test('ids are unique', () => {
402
+ const ids = res.body.items.map((item) => item.id)
403
+ assert.equal(new Set(ids).size, ids.length)
404
+ })
405
+
406
+ ata.test('total is the sum of the lines', () => {
407
+ const sum = res.body.lines.reduce((total, line) => total + line.amount, 0)
408
+ assert.equal(res.body.total, sum)
409
+ })
410
+ ```
411
+
412
+ - `assert` is Node's strict `assert`, also reachable as `ata.assert`.
413
+ - `fn` may be `async`. The step waits for it, whether or not the script does.
414
+ - An endpoint base's named checks are never replaced by a step's own (SPEC.md §2.6).
415
+
416
+ ---
417
+
418
+ ## Variables
419
+
420
+ ### `ata.get`
421
+
422
+ ```js
423
+ ata.get(name)
424
+ ```
425
+
426
+ A variable's current value, from whichever layer sets it: the project, the collection,
427
+ the environment, a data file row, or a value saved or set earlier in the run.
428
+ `undefined` when no layer does.
429
+
430
+ ```js
431
+ ata.expectResponseStatusCodeToBe(ata.get('expectedStatus'))
432
+ ```
433
+
434
+ A value comes back with its type. Every value from a CSV data file is text.
435
+
436
+ ### `ata.set`
437
+
438
+ ```js
439
+ ata.set(name, value, options?)
440
+ ```
441
+
442
+ Sets a variable, which `{{name}}` and `ata.get(name)` read from then on: in this step's
443
+ request when it is set in `before.script`, and in every step after.
444
+
445
+ ```js
446
+ ata.set('traceId', ata.uuidv7())
447
+ ata.set(
448
+ 'ids',
449
+ res.body.items.map((item) => item.id)
450
+ ) // saved as '["a","b"]'
451
+ ata.set('adminId', res.body.id, { scope: 'run' })
452
+ ```
453
+
454
+ - **Anything computed goes here**, since `vars` in a file hold plain values only
455
+ (SPEC.md §4). A collection's `before.script` runs before every step, so a value set
456
+ there is fresh for each.
457
+ - A string, number, boolean or `null` is kept as it is. An object or array is saved as
458
+ its JSON text, so `{{ids}}` writes the list into a JSON body, and `forEach: '{{ids}}'`
459
+ on a setup or teardown step sends a request for each item. Read it back in code with
460
+ `JSON.parse(ata.get('ids'))`. `undefined` is saved as `null`.
461
+ - **In a collection with a data file**, a value lasts the rest of its row.
462
+ `{ scope: 'run' }` keeps it for every row after, and for teardown (SPEC.md §2.10).
463
+ Any other `scope` is an error.
464
+ - Nothing is written to a file.
465
+ - SPEC.md §4 gives the order in which layers win.
466
+
467
+ ---
468
+
469
+ ## Skipping steps
470
+
471
+ ### `ata.skip`
472
+
473
+ ```js
474
+ ata.skip(reason?)
475
+ ```
476
+
477
+ In `before.script` only. The request is not sent, and the step is reported as skipped,
478
+ with the reason. A skipped step never fails a run.
479
+
480
+ ```js
481
+ if (!ata.get('adminToken')) ata.skip('no admin account in this environment')
482
+ ```
483
+
484
+ - The script runs to its end. No later `before.script` runs for the step: when the
485
+ collection's skips it, the step's own does not run.
486
+ - In `tests` it is an error, because the request has been sent. Use `ata.skipRest`
487
+ there.
488
+ - To skip a step depending on a feature flag, give it `flags:` (SPEC.md §2.9).
489
+
490
+ ### `ata.skipRest`
491
+
492
+ ```js
493
+ ata.skipRest(reason?)
494
+ ```
495
+
496
+ Skips the steps after this one, each reported as skipped with the reason.
497
+
498
+ ```js
499
+ if (res.status === 404) ata.skipRest('no such account, so nothing more to check')
500
+ ```
501
+
502
+ - In `tests`, this step's own result stands. In `before.script`, this step is skipped
503
+ too.
504
+ - It skips only the current row. The next row of a data file, and teardown, still run.
505
+ - The first reason given stands.
506
+
507
+ ---
508
+
509
+ ## Feature flags
510
+
511
+ ### `ata.flag`
512
+
513
+ ```js
514
+ ata.flag(name)
515
+ ```
516
+
517
+ A feature flag's value in this run: a string, number or boolean, from the environment,
518
+ the flag command or an override (SPEC.md §2.9). Use it to check something different
519
+ when a flag is on, rather than skip a step.
520
+
521
+ ```js
522
+ if (ata.flag('newCheckout')) {
523
+ ata.expectResponseBodyToHaveProperty('total.currency', 'USD')
524
+ }
525
+ ```
526
+
527
+ A flag the run does not know is an error, so a misspelled name stops the script rather
528
+ than quietly reading as off.
529
+
530
+ ---
531
+
532
+ ## Generated values
533
+
534
+ ### `ata.uuid`
535
+
536
+ ```js
537
+ ata.uuid()
538
+ ```
539
+
540
+ A random (version 4) UUID, such as `f397d495-8679-42de-8b69-d6ef86b3f5a1`.
541
+
542
+ ### `ata.uuidv7`
543
+
544
+ ```js
545
+ ata.uuidv7()
546
+ ```
547
+
548
+ A time-ordered (version 7) UUID, such as `01a0f78d-cecb-73c4-ac6e-557141551574`. Ids made
549
+ later sort later, which suits a request id or a record that must read in creation
550
+ order.
551
+
552
+ ### `ata.randomInt`
553
+
554
+ ```js
555
+ ata.randomInt(min, max)
556
+ ```
557
+
558
+ A whole number from `min` to `max`, both included: `ata.randomInt(1, 6)` rolls a die.
559
+
560
+ ### `ata.date`
561
+
562
+ ```js
563
+ ata.date(format, secondsOffset?, timeZone?)
564
+ ```
565
+
566
+ The time now, moved by `secondsOffset` seconds and formatted. `secondsOffset` is `0`
567
+ when left out, and `timeZone` is `'local'`.
568
+
569
+ ```js
570
+ ata.date('%Y-%m-%d') // today: 2026-10-01
571
+ ata.date('%F', 86400, 'utc') // tomorrow, in UTC
572
+ ata.date('%FT%T%z', -3600, 'America/New_York') // an hour ago: 2026-10-01T04:15:00-0400
573
+ ```
574
+
575
+ | Specifier | Gives | Specifier | Gives |
576
+ | --------- | ---------------------------- | --------- | ---------------------------- |
577
+ | `%Y` | Year: `2026` | `%p` | `AM` or `PM` |
578
+ | `%y` | Year, two digits: `26` | `%b` | Month, short: `Oct` |
579
+ | `%m` | Month: `01`–`12` | `%B` | Month: `October` |
580
+ | `%d` | Day: `01`–`31` | `%a` | Weekday, short: `Thu` |
581
+ | `%e` | Day, space-padded: ` 1`–`31` | `%A` | Weekday: `Thursday` |
582
+ | `%H` | Hour: `00`–`23` | `%j` | Day of the year: `001`–`366` |
583
+ | `%I` | Hour: `01`–`12` | `%Z` | Time zone: `PDT` |
584
+ | `%M` | Minute: `00`–`59` | `%z` | Offset from UTC: `-0700` |
585
+ | `%S` | Second: `00`–`59` | `%s` | Epoch seconds |
586
+ | `%L` | Millisecond: `000`–`999` | `%F` | `%Y-%m-%d` |
587
+ | `%%` | `%` | `%T` | `%H:%M:%S` |
588
+
589
+ - A specifier not listed is left as written, so `%Q` stays `%Q` and a typo shows.
590
+ - A negative `secondsOffset` is in the past.
591
+ - `timeZone` is `local`, the time zone of the machine running the test; `utc`; an IANA
592
+ name, such as `America/New_York`; or a military letter. `A` to `M` are 1 to 12 hours
593
+ ahead of UTC, skipping `J`, `N` to `Y` are 1 to 12 hours behind, and `Z` is UTC, so
594
+ `U` is -08:00, not UTC. `IST` is India.