@schwabyio/gta 0.12.0 → 0.14.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/SPEC.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # The Gravity file format
2
2
 
3
- Version 0.9
3
+ Version 0.14.0, the version of Gravity and `gta` that reads it: the two are released
4
+ together.
4
5
 
5
6
  This document specifies the YAML files that **Gravity**, the desktop app, and **`gta`**,
6
7
  the command-line runner, read and write. Together they make up Gravity Test
@@ -26,18 +27,19 @@ is a complete, valid project to start from.
26
27
 
27
28
  ## At a glance
28
29
 
29
- | File | Where | What it is | § |
30
- | ----------------------------- | ------------------------------------------ | ------------------------------------------- | ---- |
31
- | `collections/<id>.yml` | `collections/`, or one directory inside it | A collection: requests run in order | §2 |
32
- | `collections/<id>.csv\|.json` | Beside its collection | A data file: the collection runs once a row | §2.8 |
33
- | `environments/<name>.yml` | `environments/` | Variables, secrets and flags for one target | §6 |
34
- | `project.yml` | The project folder | Name, global project, variables, trust | §1.1 |
35
- | `settings.yml` | The project folder | How `gta` runs the project | §1.3 |
36
- | `requests/<id>.yml` | `requests/`, or one directory inside it | A request set, run by `use:` | §2.5 |
37
- | `endpoints/<id>.yml` | `endpoints/`, or one directory inside it | Defaults and checks per method and path | §2.6 |
38
- | `bases/<id>.yml` | `bases/`, or one directory inside it | A base collection, for `extends:` | §2.7 |
39
- | `checks/<name>.js` | `checks/` | Shared check functions | §5 |
40
- | `.env` | The project folder, never committed | Values for secrets | §6 |
30
+ | File | Where | What it is | § |
31
+ | ----------------------------- | --------------------------------------- | ------------------------------------------- | ---- |
32
+ | `collections/<id>.yml` | `collections/`, or one folder inside it | A collection: requests run in order | §2 |
33
+ | `collections/<id>.csv\|.json` | Beside its collection | A data file: the collection runs once a row | §2.8 |
34
+ | `environments/<name>.yml` | `environments/` | Variables, secrets and flags for one target | §6 |
35
+ | `project.yml` | The project folder | Name, global project, variables, trust | §1.1 |
36
+ | `settings.yml` | The project folder | How `gta` runs the project | §1.3 |
37
+ | `rules.yml` | The project folder | How the project's files are written | §1.4 |
38
+ | `requests/<id>.yml` | `requests/`, or one folder inside it | A request set, run by `use:` | §2.5 |
39
+ | `endpoints/<id>.yml` | `endpoints/`, or one folder inside it | Defaults and checks per method and path | §2.6 |
40
+ | `bases/<id>.yml` | `bases/`, or one folder inside it | A base collection, for `extends:` | §2.7 |
41
+ | `checks/<name>.js` | `checks/` | Shared check functions | §5 |
42
+ | `.env` | The project folder, never committed | Values for secrets | §6 |
41
43
 
42
44
  The smallest project `gta` runs is three files:
43
45
 
@@ -89,9 +91,10 @@ A **project** is a folder holding `collections/` and, usually, `environments/`:
89
91
  payments/ a project
90
92
  ├── project.yml optional (§1.1)
91
93
  ├── settings.yml how gta runs it (§1.3)
94
+ ├── rules.yml how its files are written (§1.4)
92
95
  ├── collections/
93
96
  │ ├── smoke.yml a collection
94
- │ └── checkout/ a directory grouping collections: one level only
97
+ │ └── checkout/ a folder grouping collections: one level only
95
98
  │ ├── sessions.yml
96
99
  │ ├── sessions.csv its data file (§2.8)
97
100
  │ └── refunds.yml
@@ -106,17 +109,17 @@ payments/ a project
106
109
  └── .env secret values; not committed (§6)
107
110
  ```
108
111
 
109
- **Every `.yml` file directly in `collections/`, or in a directory one level down, is a
112
+ **Every `.yml` file directly in `collections/`, or in a folder one level down, is a
110
113
  collection.** Nothing else is. Discovery is exact: no other `.yml` in a repository is
111
114
  mistaken for a collection. A file that does not parse is reported as a broken
112
115
  collection, never skipped in silence.
113
116
 
114
- - A directory inside a directory of `collections/` is reported as a problem and not
117
+ - A folder inside a folder of `collections/` is reported as a problem and not
115
118
  read. `requests/`, `endpoints/` and `bases/` are read to the same depth, and
116
119
  `checks/` only at its top level.
117
- - Names starting with `.` are ignored, as are the directories `node_modules`, `.git`,
120
+ - Names starting with `.` are ignored, as are the folders `node_modules`, `.git`,
118
121
  `reports`, `test-results`, `out` and `dist`.
119
- - Directories carry no configuration and need no file of their own. They group
122
+ - Folders carry no configuration and need no file of their own. They group
120
123
  collections for display and for running a group.
121
124
 
122
125
  A project is any folder: the root of a repository, or one service of a monorepo. A
@@ -160,8 +163,8 @@ The file is optional, and so is every key in it. Any other key is an error.
160
163
  | `tls.ca` | list of relative paths | none | Certificate files that requests trust (below). |
161
164
 
162
165
  **`uses`** names a **global project**: an ordinary project whose `project.yml`
163
- variables, `environments/`, `requests/`, `endpoints/`, `bases/`, `checks/` and
164
- `settings.yml` every project using it shares.
166
+ variables, `environments/`, `requests/`, `endpoints/`, `bases/`, `checks/`,
167
+ `settings.yml` and `rules.yml` every project using it shares.
165
168
 
166
169
  - It must be a relative path. An absolute path is refused, since it would only work on
167
170
  one machine. Write it with `/`; `\` reads the same.
@@ -170,7 +173,15 @@ variables, `environments/`, `requests/`, `endpoints/`, `bases/`, `checks/` and
170
173
  - An environment in the global project merges under the project's environment of the
171
174
  same name, key by key, and the project's values win. An environment only the global
172
175
  project has is available too.
173
- - The global project's `settings.yml` lies under the project's the same way (§1.3).
176
+ - The global project's `settings.yml` lies under the project's the same way (§1.3), and
177
+ so does its `rules.yml` (§1.4).
178
+ - A global project's own `collections/` is **not** shared: a project using it never
179
+ sees or runs those collections. A global project needs none. It may have one for a
180
+ single purpose: **testing what it shares**. A collection there can `use:` its request
181
+ sets, `extends:` its bases and call its checks, against its own environments, so a
182
+ broken shared piece fails in one place, before every project relying on it does.
183
+ Those collections run only when the global project itself does, as a project in
184
+ Gravity or with `gta` in its folder, such as its own CI job.
174
185
 
175
186
  **`tls.ca`** lists certificate files that requests trust, for a server whose certificate
176
187
  a company or local CA signed, or a server's own self-signed certificate. A request
@@ -203,11 +214,11 @@ Projects are shared between macOS, Windows and Linux, and read the same on all t
203
214
  `use:` or `extends:` name, `uses`, a `tls.ca` file, a file a body sends, and an
204
215
  environment's file name. One that differs only in case is an error on every platform,
205
216
  naming the spelling on disk. The folders and files of §1 are lower case.
206
- - **No two names in one directory may differ only in case**, such as `Checkout/` and
217
+ - **No two names in one folder may differ only in case**, such as `Checkout/` and
207
218
  `checkout/`: Linux can hold both, but a macOS or Windows checkout only one. This
208
219
  applies in `collections/`, `requests/`, `endpoints/`, `bases/`, `environments/` and
209
220
  `checks/`. Collection ids are unique ignoring case too (§2).
210
- - File, directory and environment names must avoid what Windows refuses: the characters
221
+ - File, folder and environment names must avoid what Windows refuses: the characters
211
222
  `< > : " / \ | ? *`, control characters, a trailing dot or space, and the names `CON`,
212
223
  `PRN`, `AUX`, `NUL`, `CONIN$`, `CONOUT$`, `COM1`–`COM9` and `LPT1`–`LPT9`, with or
213
224
  without an extension. A file with such a name, made on macOS or Linux, is reported.
@@ -282,7 +293,7 @@ global project's `settings.yml`, the project's, the environment variable, the fl
282
293
  | --------------------- | ----------------------------------------------------------- |
283
294
  | `gta get` | Nothing. It lists what `gta all` would run. |
284
295
  | `gta all` | Every collection, except those with `exclude: true` (§2.4). |
285
- | `gta smoke,checkout/` | The collections and directories named, in that order. |
296
+ | `gta smoke,checkout/` | The collections and folders named, in that order. |
286
297
 
287
298
  `gta get` also reports each `use:` and `extends:` that would stop a run, and each file a
288
299
  body names that is in neither the project nor its global project (§2.2), in every
@@ -290,11 +301,159 @@ collection, including those `gta all` leaves out (Appendix A). A file path with
290
301
  `{{variables}}` is left to the run. It exits `1` when it finds one, or a broken
291
302
  collection, and `0` otherwise.
292
303
 
293
- A collection is named by its `id`, or by its place (`checkout/sessions`). A directory is
294
- named by its name, and `checkout/` names only the directory. `--flag name=value` sets a
304
+ A collection is named by its `id`, or by its place (`checkout/sessions`). A folder is
305
+ named by its name, and `checkout/` names only the folder. `--flag name=value` sets a
295
306
  feature flag (§2.9), and `--json` prints the results as JSON. The exit code is `0` when
296
307
  everything passed, `1` when something failed, and `2` when `gta` could not run.
297
308
 
309
+ ### 1.4 `rules.yml`
310
+
311
+ How a project's files are named, laid out and written, so that everyone working on it,
312
+ people and coding agents alike, keeps it consistent. It sits beside `project.yml`, is
313
+ committed, and is optional. `gta lint` checks the project against it. Gravity, the
314
+ desktop app, marks each file, folder and step that breaks a rule, and will not create or
315
+ rename a file or folder that would.
316
+
317
+ ```yaml
318
+ ids:
319
+ collections: kebab-case # or a pattern, such as '{folder}-[a-z0-9-]+'
320
+ requests: camelCase
321
+ layout:
322
+ folders: required # every collection in a folder of collections/
323
+ folderNames: [accounts, payments, smoke]
324
+ maxSteps: 30
325
+ steps:
326
+ names: required
327
+ url: ^\{\{baseUrl\}\} # no host written out
328
+ docs:
329
+ collections: required
330
+ tags:
331
+ allowed: [smoke, regression, slow]
332
+ tests:
333
+ only: [gta] # tests call the gta.* functions, and nothing else
334
+ statusCode: required
335
+ guide: |
336
+ Name each step for what it proves. Logins go through `requests/login`.
337
+ ```
338
+
339
+ **Rules never change a run.** A file that breaks one opens, sends and runs as before.
340
+ `gta lint` reports it, and a CI job running `gta lint` fails on it. Gravity marks it, and
341
+ flags what `tests.only` does not allow as a script is typed.
342
+
343
+ | Rule | Value | What it checks |
344
+ | -------------------- | ---------------------- | ------------------------------------------------------------------------- |
345
+ | `ids.collections` | style or pattern | Each collection's id, its file name (§2). |
346
+ | `ids.requests` | style or pattern | Each request set's id (§2.5). |
347
+ | `ids.bases` | style or pattern | Each base collection's id (§2.7). |
348
+ | `ids.endpoints` | style or pattern | Each endpoints file's id (§2.6). |
349
+ | `layout.folders` | `required` | Every collection sits in a folder of `collections/`, none at its top. |
350
+ | `layout.folderNames` | list, style or pattern | Each folder of `collections/`: one of the list, or following the format. |
351
+ | `layout.maxSteps` | integer ≥ 1 | The most steps a collection has in `steps`; `setup` and `teardown` aside. |
352
+ | `steps.names` | `required` | Every step has a `name`, and no other step of its file has the same one. |
353
+ | `steps.url` | pattern | Every request step's URL, as written, matches it (below). |
354
+ | `docs.collections` | `required` | Every collection has `docs`. |
355
+ | `docs.requests` | `required` | Every request set has `docs`. |
356
+ | `docs.steps` | `required` | Every step of a collection or request set has `docs`, in every list. |
357
+ | `tags.allowed` | list of tags | Every tag on a collection or a step is one of these. |
358
+ | `tags.collections` | `required` | Every collection has `tags` of its own. |
359
+ | `tests.only` | list (below) | What a `tests` script may call. |
360
+ | `tests.everyStep` | `required` | Every step that sends a request or reads a connection is checked (below). |
361
+ | `tests.statusCode` | `required` | Every request step's checks include its status code (below). |
362
+
363
+ - Any other group or rule is an error, as is a value of the wrong type.
364
+ - A rule that takes `required` also takes `optional`, which turns it off.
365
+ - A **style** is one of `kebab-case` (`create-user`), `snake_case` (`create_user`),
366
+ `camelCase` (`createUser`) and `PascalCase` (`CreateUser`). Lower case, in the first two,
367
+ includes digits: `404-handling` is kebab-case.
368
+ - A **pattern** is a JavaScript regular expression that the whole name must match, as if
369
+ it began with `^` and ended with `$`. `{folder}` in it stands for the folder the file is
370
+ in, so `{folder}-[a-z0-9-]+` asks for `collections/payments/payments-refunds.yml`. A
371
+ value that is not a style and has none of a pattern's characters (`^`, `$`, `.`, `*`,
372
+ `+`, `?`, `(`, `)`, `[`, `]`, `{`, `}`, `|` or a backslash) is an error, so a
373
+ misspelled style is not taken for a pattern.
374
+ - Rules check the project's own files, in `collections/`, `requests/`, `bases/` and
375
+ `endpoints/`. A global project's files follow the global project's `rules.yml`, checked
376
+ when `gta lint` runs in its folder.
377
+ - `steps.*`, `docs.steps`, `tests.everyStep` and `tests.statusCode` are about the steps of
378
+ collections and request sets. An endpoint is a method and a path pattern, not a step
379
+ that runs, so they leave endpoints files alone.
380
+
381
+ **`steps.url`** is a JavaScript regular expression that each request step's URL, as
382
+ written with its `{{variables}}`, must match. Unlike an id pattern it is not anchored:
383
+ `^\{\{baseUrl\}\}` asks that every URL start with `{{baseUrl}}`, so no host is written
384
+ out in a step. A use step and a step reading a connection have no URL of their own.
385
+
386
+ **`tests.everyStep` and `tests.statusCode`** count every script that runs after a step's
387
+ response, wherever it is written: the step's own `tests`, its file's, its base
388
+ collection's (§2.7) and its endpoint's (§2.6), unless the step has `base: false`. With
389
+ `tests.statusCode`, one of them calls `gta.expectResponseStatusCodeToBe`, in any branch,
390
+ or calls a check function (§5) whose own code does. A use step is left to its request
391
+ set, whose steps are checked there. A step reading a connection needs tests under
392
+ `tests.everyStep`, but has no status code of its own to check.
393
+
394
+ **`tests.only`** lists what a `tests` script may call. It must list `gta`:
395
+
396
+ | Entry | Allows |
397
+ | ---------- | --------------------------------------------------------- |
398
+ | `gta` | The `gta.*` functions (§5), except `gta.test`. |
399
+ | `gta.test` | Named checks of your own, with any code inside them (§5). |
400
+ | `checks` | The project's check files, `checks.<file>.<function>()`. |
401
+ | `console` | `console.log()` and the rest. |
402
+
403
+ With `only: [gta]`, a `tests` script holds calls to `gta.*` functions and nothing else:
404
+
405
+ - **Every argument is a value**: a string, number, `true`, `false`, `null` or regular
406
+ expression, a template string, a list or object of values, or `new RegExp(…)` of
407
+ values. It may read `res`, `req`, `params`, `endpoint` and `item` (§5), call another
408
+ `gta.*` function such as `gta.get('id')`, or `JSON.parse` a value, since `gta.set` keeps
409
+ a list or object as JSON text.
410
+ - **An `if` may choose by feature flag** (§2.9): its condition is `gta.flag()` calls
411
+ compared with values, joined by `!`, `&&` and `||`, and what it holds follows the same
412
+ rules.
413
+ - **Nothing else**: no variables, loops, functions of your own, `assert`, `checks.*` or
414
+ `gta.test`. This is checked on the script's syntax, so a call cannot be hidden by
415
+ renaming it (`const c = checks`).
416
+
417
+ ```yaml
418
+ tests: |
419
+ gta.expectResponseStatusCodeToBe(200)
420
+ gta.expectResponseBodyToHaveProperty('owner', gta.get('userId'))
421
+ gta.set('nextPage', res.body.links.next)
422
+ if (gta.flag('newCheckout') === true) {
423
+ gta.expectResponseBodyToHaveProperty('version', 2)
424
+ }
425
+ ```
426
+
427
+ `tests.only` checks every `tests` script: a collection's own and each of its steps', and
428
+ those of request sets, base collections and endpoints files. `before.script` is not
429
+ checked.
430
+
431
+ **`guide`** is Markdown, for what no rule can check: how steps are named, which request
432
+ set a login goes through, what a collection is for. `gta rules` prints it after the
433
+ rules, and Gravity shows it in Project settings. Each file's guide is read, the global
434
+ project's first, so a project adds to a shared guide rather than replacing it.
435
+
436
+ **From the global project.** A global project (§1.1) can have a `rules.yml` too, and it
437
+ lies under the project's, rule by rule. There is no switch to turn this off: a project
438
+ changes a shared rule by setting it in its own file, and turns it off with `null`, or
439
+ `optional` for a `required` rule. A group set to `null`, such as `tags: null`, turns off
440
+ every rule in it. Neither file is required.
441
+
442
+ **Checking.** From a project folder, or a global project's:
443
+
444
+ | Command | Does |
445
+ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
446
+ | `gta lint` | Checks every file against the rules, and prints each finding at its file and line, with its rule and the file the rule came from. Exits `1` on a finding. |
447
+ | `gta rules` | Lists each rule set, its value, the file it came from and what it means, rules turned off included, then the guide. |
448
+
449
+ - Neither needs `settings.yml` or runs anything, and both take `--json`.
450
+ - A file that will not parse is a finding, since no rule could be checked in it.
451
+ - Both exit `2` when a rule is not valid, or `project.yml` names a global project that
452
+ cannot be used: its rules would quietly go unchecked.
453
+ - A coding agent can run `gta rules` to learn a project's conventions before writing, and
454
+ `gta lint --json` after, to fix what it finds. Gravity offers to add a section saying so
455
+ to the project's `AGENTS.md`, which coding agents read; nothing is written until asked.
456
+
298
457
  ---
299
458
 
300
459
  ## 2. Collection file
@@ -367,7 +526,7 @@ exactly. `collections/checkout/sessions.yml` starts `id: sessions`.
367
526
  - **It must match the file name.** A file whose `id` is missing or different is a
368
527
  broken collection. Renaming a file means changing its `id` too.
369
528
  - **It must be unique in its home, ignoring case.** No two files in a project's
370
- `collections/` may share an id, including files in different directories of it; the
529
+ `collections/` may share an id, including files in different folders of it; the
371
530
  same holds for `requests/`, `bases/` and `endpoints/`. Case is ignored because macOS
372
531
  and Windows file systems ignore it. Every file sharing an id is broken.
373
532
  - **It is letters, digits and `- _ .`, starting with a letter or digit**:
@@ -388,27 +547,31 @@ key**, in capitals, whose value is the URL as a string.
388
547
 
389
548
  Methods: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`.
390
549
 
391
- | Key | Type | Required | Meaning |
392
- | ---------- | ------------ | -------- | ------------------------------------------------------------------------- |
393
- | `<METHOD>` | string | **yes** | The URL, query string included. |
394
- | `name` | string | no | Display name. Defaults to the method and URL. |
395
- | `headers` | map | no | Over the collection's headers (§2.3). |
396
- | `body` | map | no | Exactly one kind of body (§2.2). |
397
- | `settings` | map | no | Over the collection's settings (§2.3). |
398
- | `before` | map | no | `script:` run before the request (§5). |
399
- | `tests` | string | no | The checks, as JavaScript: calls on `gta` (§3) and any other code (§5). |
400
- | `tags` | list of tags | no | The step's own tags. Only with `stepTags: true` on the collection (§2.4). |
401
- | `flags` | map | no | Feature flags the step needs (§2.9). |
402
- | `forEach` | string | no | Send the request once for each item of a list (below). |
403
- | `useTests` | `true` | no | In a request set: the use step's `tests` check this response (§2.5). |
404
- | `base` | `false` | no | `false` leaves the step's endpoint base out (§2.6). |
405
- | `docs` | string | no | Markdown. |
406
-
407
- - A step with no method key, or with two, is an error.
550
+ | Key | Type | Required | Meaning |
551
+ | ------------ | ------------ | -------- | ------------------------------------------------------------------------- |
552
+ | `<METHOD>` | string | **yes** | The URL, query string included. |
553
+ | `name` | string | no | Display name. Defaults to the method and URL. |
554
+ | `headers` | map | no | Over the collection's headers (§2.3). |
555
+ | `body` | map | no | Exactly one kind of body (§2.2). |
556
+ | `settings` | map | no | Over the collection's settings (§2.3). |
557
+ | `before` | map | no | `script:` run before the request (§5). |
558
+ | `tests` | string | no | The checks, as JavaScript: calls on `gta` (§3) and any other code (§5). |
559
+ | `tags` | list of tags | no | The step's own tags. Only with `stepTags: true` on the collection (§2.4). |
560
+ | `flags` | map | no | Feature flags the step needs (§2.9). |
561
+ | `forEach` | string | no | Send the request once for each item of a list (below). |
562
+ | `useTests` | `true` | no | In a request set: the use step's `tests` check this response (§2.5). |
563
+ | `connection` | string | no | Keep the event stream this request opens as a connection (§2.11). |
564
+ | `base` | `false` | no | `false` leaves the step's endpoint base out (§2.6). |
565
+ | `docs` | string | no | Markdown. |
566
+
567
+ - A step with two method keys is an error, and so is a step with none, unless it is a
568
+ use step or reads a connection.
408
569
  - **The URL is authoritative, query string included.** There is no separate block of
409
570
  query parameters. Editors show a parameter table as a view over the URL.
410
571
  - A step may instead run a request set with `use:` (§2.5). A use step has no method
411
572
  key.
573
+ - A step may also read a connection, an event stream an earlier step keeps open, with
574
+ `connection:` and no method key (§2.11).
412
575
  - **Any other key is an error**, so a misspelled key such as `heders:` fails at once
413
576
  rather than being ignored. A method key in lower case (`get:`) is reported as such.
414
577
 
@@ -532,12 +695,65 @@ body:
532
695
  **`settings`** on a collection apply to every step; a step's own settings merge over
533
696
  them. Any other key is an error.
534
697
 
535
- | Key | Type | Default | Meaning |
536
- | ----------------- | ----------- | ------- | -------------------------------------------------------------- |
537
- | `timeout` | number ≥ 0 | `0` | Milliseconds for the whole request; `0` means no limit. |
538
- | `followRedirects` | boolean | `true` | Follow 3xx responses. |
539
- | `maxRedirects` | integer ≥ 0 | `5` | The most redirects followed. |
540
- | `encodeUrl` | boolean | `true` | Percent-encode what a hand-typed URL left raw, before sending. |
698
+ | Key | Type | Default | Meaning |
699
+ | ----------------- | ----------- | ------- | ------------------------------------------------------------------------------------------------------ |
700
+ | `timeout` | number ≥ 0 | `0` | Milliseconds for the whole request; `0` means no limit. For an event stream, until its headers arrive. |
701
+ | `followRedirects` | boolean | `true` | Follow 3xx responses. |
702
+ | `maxRedirects` | integer ≥ 0 | `5` | The most redirects followed. |
703
+ | `encodeUrl` | boolean | `true` | Percent-encode what a hand-typed URL left raw, before sending. |
704
+ | `maxEvents` | integer ≥ 0 | `0` | Stop reading an event stream after this many events; `0` means no limit. |
705
+ | `streamTimeout` | number ≥ 0 | `0` | Stop reading an event stream this many milliseconds after its headers; `0` means no limit. |
706
+ | `untilEvent` | string | none | Stop reading an event stream after the first event of this name, its `event:` line. |
707
+
708
+ **Event streams.** A response whose `Content-Type` is `text/event-stream` (Server-Sent
709
+ Events) is read as it arrives, whatever the method, and checked as a list of events
710
+ (§3). Nothing in the step marks it as a stream. Reading stops at whichever comes first:
711
+
712
+ - the server closes the stream
713
+ - `maxEvents` events have arrived
714
+ - `streamTimeout` milliseconds have passed since its headers arrived
715
+ - the first event named `untilEvent` has arrived
716
+ - 1,000 events or 10 MB, a limit no setting lifts
717
+
718
+ ```yaml
719
+ - name: price stream
720
+ GET: '{{baseUrl}}/prices/stream?symbol=ACME'
721
+ headers:
722
+ Accept: text/event-stream
723
+ settings:
724
+ maxEvents: 3
725
+ streamTimeout: 5000
726
+ tests: |
727
+ gta.expectResponseStatusCodeToBe(200)
728
+ gta.expectResponseBodyToHaveProperty('[0].event', 'subscribed')
729
+ gta.expectResponseBodyToHaveProperty('[1].data.price', 100)
730
+ ```
731
+
732
+ - Each way of stopping is a normal end, not an error, and the checks run on the events
733
+ that arrived. A check that wanted more fails with the path it missed.
734
+ - Stopped at a number of events, the body ends with the last one.
735
+ - Cancel, or a connection that drops mid-stream, is an error, as for any request.
736
+ - `maxEvents`, `streamTimeout` and `untilEvent` do nothing to any other response.
737
+ - In the desktop app, the events show as they arrive, and Stop ends the reading as
738
+ these do, so the checks run on what came.
739
+ - A step that keeps its stream open for later steps is a connection (§2.11).
740
+
741
+ **Resuming.** A stream is never reconnected: the server closing it ends the step. To
742
+ test that a stream resumes, capture the last event's id and send it as `Last-Event-ID`
743
+ in a later step:
744
+
745
+ ```yaml
746
+ - name: first part
747
+ GET: '{{baseUrl}}/feed'
748
+ tests: |
749
+ gta.set('lastId', res.body.at(-1).id)
750
+ - name: the rest
751
+ GET: '{{baseUrl}}/feed'
752
+ headers:
753
+ Last-Event-ID: '{{lastId}}'
754
+ tests: |
755
+ gta.expectResponseBodyToHaveProperty('[0].id', '4')
756
+ ```
541
757
 
542
758
  **`headers`** is a map from header name to one of:
543
759
 
@@ -590,13 +806,13 @@ else selects it. With `stepTags: true`, its steps whose tags match are dropped f
590
806
  was selected.
591
807
 
592
808
  **`exclude: true`** leaves a collection out of group runs: `gta all`, with or without
593
- tags, and a directory named to `gta`. Named on its own, it still runs. Use it for work in
809
+ tags, and a folder named to `gta`. Named on its own, it still runs. Use it for work in
594
810
  progress, a manual-only collection, or one waiting on a fix. `gta` lists what it left
595
811
  out, so a suite never shrinks without saying so.
596
812
 
597
813
  ### 2.5 Request sets and `use:`
598
814
 
599
- A **request set** is a collection in `requests/`, directly or one directory down, with a
815
+ A **request set** is a collection in `requests/`, directly or one folder down, with a
600
816
  `params:` key: the inputs it takes. A step elsewhere runs it with **`use:`**, and passes
601
817
  values with **`with:`**.
602
818
 
@@ -650,7 +866,7 @@ method key, `headers`, `body`, `settings` or `before` on it is an error, and `wi
650
866
  without `use` is an error too.
651
867
 
652
868
  - **Finding the set.** `use: login` is `requests/login.yml` in the project, else in its
653
- global project. `use: auth/login` is one directory down. `use: global:login` looks
869
+ global project. `use: auth/login` is one folder down. `use: global:login` looks
654
870
  only in the global project.
655
871
  - **`with:`** gives plain values; a param left out takes its default. A string may hold
656
872
  `{{variables}}`, resolved as the set's first request starts, just after the
@@ -815,7 +1031,8 @@ userId,expectedStatus,iterationLabel
815
1031
  string**: a zip code `01234` stays `01234`. A short row leaves its last columns empty;
816
1032
  a row with more values than the header is an error.
817
1033
  - **JSON** is an array of objects, one per row. Values keep their type, which must be
818
- string, number, boolean or null.
1034
+ string, number, boolean or null. A row must not name a column twice: JSON readers keep
1035
+ only the last value, so it is an error rather than a value quietly lost.
819
1036
  - **`.csv` wins** when both exist. The name must match exactly, case included.
820
1037
  - A data file must be at most 10 MB and have at least one row. The header must not have
821
1038
  an empty or repeated column name. A data file that will not read makes the collection
@@ -934,12 +1151,73 @@ steps: # once per row of approved-domains.csv, as before
934
1151
  - Reports name their results `setup › log in` and `teardown › remove the grant`. Running
935
1152
  a single step in the desktop app does not run them.
936
1153
 
1154
+ ### 2.11 Connections: a stream across steps
1155
+
1156
+ A request whose response is an event stream (§2.3) can keep it open for later steps, as
1157
+ a **connection** named by `connection:`. A later step with `connection:` and no method
1158
+ key sends nothing: it reads the events the connection holds. The steps between can do
1159
+ what those events are about:
1160
+
1161
+ ```yaml
1162
+ steps:
1163
+ - name: watch orders
1164
+ GET: '{{baseUrl}}/orders/events'
1165
+ connection: orders # keep the stream open as "orders"
1166
+ settings:
1167
+ untilEvent: subscribed # read until the server confirms, then go on
1168
+ - name: place order
1169
+ POST: '{{baseUrl}}/orders'
1170
+ body:
1171
+ json: '{ "sku": "ACME-1" }'
1172
+ tests: |
1173
+ gta.expectResponseBodyToHaveProperty('id', 'orderId', 'setAsCollectionVariable')
1174
+ - name: order created
1175
+ connection: orders # no method key: reads the connection
1176
+ settings:
1177
+ untilEvent: order.created
1178
+ streamTimeout: 5000
1179
+ tests: |
1180
+ gta.expectResponseBodyToHaveProperty('[0].event', 'order.created')
1181
+ gta.test('the order placed', () => assert.equal(res.body[0].data.id, gta.get('orderId')))
1182
+ ```
1183
+
1184
+ - **Opening.** The step sends its request and reads the stream as any step does, until
1185
+ its `maxEvents`, `streamTimeout` or `untilEvent`. Then, rather than closing it, it
1186
+ keeps reading in the background, holding the events that arrive for the next step
1187
+ that reads the connection. With none of the three set, it reads no events and goes
1188
+ straight on.
1189
+ - **Reading.** A reading step takes the events held since the last step read them,
1190
+ then waits for more, until its `maxEvents`, `streamTimeout` or `untilEvent`, or the
1191
+ server closes the stream. With none of the three set, it takes what is held and ends
1192
+ without waiting, as `held`.
1193
+ - **What a reading step sees.** Its body is the events it took (§3). Its status and
1194
+ headers are those of the response that opened the connection, `req` is that request,
1195
+ and `res.stream.at` counts from that response's headers.
1196
+ - A reading step may have `name`, `settings`, `before`, `tests`, `tags`, `flags`,
1197
+ `useTests` and `docs`, and nothing that would build a request: no `headers`, `body`,
1198
+ `base` or `forEach`. A step opening a connection has no `forEach`, and a use step has
1199
+ no `connection`.
1200
+ - **A connection lasts the run.** One opened in `setup` is read by every data row; one
1201
+ opened in a row, by that row's later steps. Every connection closes when the run ends,
1202
+ cancelled or not. A step opening a name already open closes the old connection first,
1203
+ whatever its own response is.
1204
+ - A connection the server closes keeps the events it held: the next step reads them,
1205
+ then ends with `close`. A connection holds at most 1,000 events or 10 MB; at that it
1206
+ stops reading, and the step that reads it ends with `limit`.
1207
+ - Reading a connection no step has opened is an error, and the step's scripts do not
1208
+ run.
1209
+ - **In the desktop app**, a connection opened by sending a step stays open for the steps
1210
+ sent after it, until that step is sent again, the connection is closed from above the
1211
+ step list, Run all starts, or the app quits. Run all, like `gta`, opens its own and
1212
+ closes them when it ends.
1213
+
937
1214
  ---
938
1215
 
939
1216
  ## 3. Checking a response
940
1217
 
941
- A step's checks are calls to the [xtest](https://github.com/schwabyio/xtest) functions
942
- on `gta`, in its `tests` (§5 lists them). This section is what the calls mean.
1218
+ A step's checks are calls to functions on `gta`, in its `tests` (§5 lists them).
1219
+ [FUNCTIONS.md](./FUNCTIONS.md) documents each one, with examples. This section is the
1220
+ rules they share.
943
1221
 
944
1222
  ```yaml
945
1223
  tests: |
@@ -962,14 +1240,14 @@ A key that holds a `.`, `[` or `]`, or is empty, goes in brackets as a JSON stri
962
1240
  `jwt.payload["https://example.com/id"]`, `modules[""].edition`. Reports show such keys
963
1241
  the same way.
964
1242
 
965
- As in xtest, a path can also be a list of keys: `['jwt', 'payload', 'https://example.com/id']`.
1243
+ A path can also be a list of keys: `['jwt', 'payload', 'https://example.com/id']`.
966
1244
  Each item is one key, whatever it holds, and a number is its digits, so
967
1245
  `['groups', 0, 'name']` reads an index. Every function that takes a path takes a list
968
1246
  too, and so does `pathToProperty`.
969
1247
 
970
1248
  A path that runs into a `null` before its end, such as `phoneNumber.number` when
971
- `phoneNumber` is `null`, reads as that `null` for a check that the value is `null`, as
972
- xtest read it. For any other check the property is not present.
1249
+ `phoneNumber` is `null`, reads as that `null` for a check that the value is `null`. For
1250
+ any other check the property is not present.
973
1251
 
974
1252
  ### Body conversion
975
1253
 
@@ -978,6 +1256,37 @@ top-level key, namespace prefixes and attributes are dropped, an element holding
978
1256
  text becomes that string (an empty one `""`), and a repeated element becomes an array.
979
1257
  A `text/plain` or HTML body is the single property `plaintext`.
980
1258
 
1259
+ An event stream (§2.3) is a list with one item per event:
1260
+
1261
+ - **`data`** is the event's `data:` lines joined with a line feed: their JSON value when
1262
+ they parse as JSON, so `[1].data.price` is a number, and otherwise the text, such as
1263
+ `[DONE]`.
1264
+ - **`event`** and **`id`** are there only when that event's own lines set them. Neither
1265
+ carries over to the next event.
1266
+ - Comments (`: heartbeat`) and a block with no `data:` are not events, `retry:` is not
1267
+ kept, and an event the stream ended in the middle of is dropped.
1268
+
1269
+ ```text
1270
+ event: price
1271
+ id: 41
1272
+ data: {"symbol":"ACME","price":100}
1273
+
1274
+ : heartbeat
1275
+
1276
+ data: [DONE]
1277
+
1278
+ ```
1279
+
1280
+ reads as
1281
+
1282
+ ```json
1283
+ [{ "event": "price", "id": "41", "data": { "symbol": "ACME", "price": 100 } }, { "data": "[DONE]" }]
1284
+ ```
1285
+
1286
+ The path `''` is the whole list, so `expectResponseBodyToHaveUnorderedArray('', …)` finds
1287
+ events whose order is not guaranteed. Under strict validation every event's properties
1288
+ need accounting for, so a small `maxEvents` keeps a busy stream practical.
1289
+
981
1290
  ### Values and patterns
982
1291
 
983
1292
  - A value compares with its type: `'12345'` does not equal `12345`.
@@ -1008,7 +1317,13 @@ saying so, and the checks after it still run.
1008
1317
  ### Unordered arrays
1009
1318
 
1010
1319
  `gta.expectResponseBodyToHaveUnorderedArray(path, list)` passes when the array holds
1011
- every item of a simple `list`, in any order.
1320
+ every item of a simple `list`, in any order. A `RegExp` in the list is a pattern some item
1321
+ must match as text, and so is one held by an object in the list:
1322
+ `[/^admin/, { name: /^Grace/ }]`. A pattern never matches an object or array item.
1323
+ An object in the list matches an item holding each of its properties, and the item may
1324
+ have others; a property that is itself an object is matched the same way, so
1325
+ `{ data: { status: 'reversed' } }` finds an item whose `data` has that `status` among
1326
+ other properties. An array compares whole.
1012
1327
 
1013
1328
  A list of `{ pathToProperty, expectedValue, specialHandling? }` objects describes **one**
1014
1329
  item, property by property; call it once per item. A property may appear twice, once to
@@ -1022,15 +1337,16 @@ gta.expectResponseBodyToHaveUnorderedArray('users', [
1022
1337
  ```
1023
1338
 
1024
1339
  `gta.expectResponseBodyToHaveUnorderedArrayNotThisItem(path, list)` passes when no item
1025
- matches. A `compareValue` may be a `RegExp`.
1340
+ matches. A simple list may hold patterns here too, and a `compareValue` may be a
1341
+ `RegExp`.
1026
1342
 
1027
1343
  - **Each call prefers items an earlier call did not match.** Two calls with the same
1028
1344
  description find two items when there are two, so each capture and strict validation
1029
1345
  see a different one. A sort starts this over, since its indexes name other items.
1030
- - **A list of one `notThisExpectedValue` entry reads as xtest read it.** Without strict
1031
- validation it means no item has that value, so an empty array passes. With it, it
1032
- means one item whose value is something else, as any list does. The step's last word
1033
- on strict validation decides.
1346
+ - **A list of one `notThisExpectedValue` entry depends on strict validation.** Without
1347
+ it, the entry means no item has that value, so an empty array passes. With it, the
1348
+ entry means one item whose value is something else, as any list does. The step's last
1349
+ word on strict validation decides.
1034
1350
 
1035
1351
  ### Strict validation
1036
1352
 
@@ -1159,8 +1475,8 @@ request, and `tests` checks the response:
1159
1475
 
1160
1476
  ### The `gta` object
1161
1477
 
1162
- The xtest functions keep their original names, arguments and `specialHandling` strings.
1163
- There is nothing to load, and no `startXTest` or `endXTest`.
1478
+ There is nothing to import or load. [FUNCTIONS.md](./FUNCTIONS.md) documents each
1479
+ function, with examples.
1164
1480
 
1165
1481
  | Function | In `before.script` |
1166
1482
  | ------------------------------------------------------------------------------------------------ | :----------------: |
@@ -1196,20 +1512,35 @@ row, and teardown, still run.
1196
1512
 
1197
1513
  `gta.date` formats with strftime specifiers: `%Y %y %m %d %e %H %I %M %S %L %p %b %B %a
1198
1514
  %A %j %Z %z %s %F %T %%`. An unrecognized specifier is left in the output, so a typo is
1199
- visible. `timeZone` is `local`, `utc`, an IANA name such as `America/New_York`, or one of
1200
- xtest's military zone letters (`U` is -08:00, not UTC).
1515
+ visible. `timeZone` is `local`, `utc`, an IANA name such as `America/New_York`, or a
1516
+ military zone letter (`U` is -08:00, not UTC).
1201
1517
 
1202
1518
  ### Other globals
1203
1519
 
1204
- | Global | What it is |
1205
- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1206
- | `res` | `tests` only: `status`, `statusText`, `headers` (lower-cased names), `header(name)`, `body` (parsed JSON, converted XML, or text), `text`, `time` (ms), `size` (bytes). |
1207
- | `req` | `method`, `url`, `headers`, `body`: as sent in `tests`, as written in `before.script`, where a script may change `headers` and `body` (below). |
1208
- | `assert` | Node's strict `assert`, for use inside `gta.test`. |
1209
- | `console` | Captured into the step's result. |
1210
- | `params` | A request set's params (§2.5), in its own scripts and in the tests of the use step running it. |
1211
- | `endpoint` | An endpoint base's `{name}` values (§2.6), in its scripts and in every script of a step under it. |
1212
- | `checks` | The project's check files (below). |
1520
+ | Global | What it is |
1521
+ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1522
+ | `res` | `tests` only: `status`, `statusText`, `headers` (lower-cased names), `header(name)`, `body` (parsed JSON, converted XML, an event stream's events, or text), `text`, `time` (ms), `size` (bytes), and for an event stream `stream` (below). |
1523
+ | `req` | `method`, `url`, `headers`, `body`: as sent in `tests`, as written in `before.script`, where a script may change `headers` and `body` (below). |
1524
+ | `assert` | Node's strict `assert`, for use inside `gta.test`. |
1525
+ | `console` | Captured into the step's result. |
1526
+ | `params` | A request set's params (§2.5), in its own scripts and in the tests of the use step running it. |
1527
+ | `endpoint` | An endpoint base's `{name}` values (§2.6), in its scripts and in every script of a step under it. |
1528
+ | `checks` | The project's check files (below). |
1529
+
1530
+ **`res` for an event stream** (§2.3): `res.body` is its list of events, as checks see it
1531
+ (§3), `res.text` the stream as received, and `res.time` runs until the reading stopped.
1532
+ `res.stream` has the rest, and is absent for any other response:
1533
+
1534
+ - `endedBy`: what stopped the reading: `close`, `maxEvents`, `streamTimeout`,
1535
+ `untilEvent`, `limit`, `stopped` (the desktop app's Stop button), or `held` (a step on
1536
+ a connection that waited for nothing, §2.11)
1537
+ - `at`: for each event, the milliseconds from the response headers to its arrival
1538
+ - `connection`: for a step that opens or reads a connection, `{ name, open }`: its name,
1539
+ and whether it was still open when the step ended
1540
+
1541
+ ```js
1542
+ gta.test('first price within 2s', () => assert.ok(res.stream.at[0] < 2000))
1543
+ ```
1213
1544
 
1214
1545
  Also available are the language itself and the web-standard globals: timers, `URL`,
1215
1546
  `URLSearchParams`, `TextEncoder`, `TextDecoder`, `atob`, `btoa`, `structuredClone` and
@@ -1236,7 +1567,10 @@ delete req.headers['X-Debug']
1236
1567
 
1237
1568
  - `req.body` is text, and can be changed for a `json`, `xml`, `text` or `graphql` body.
1238
1569
  A form, multipart or file body is built from its parts, so it cannot.
1239
- - `req.headers` is a map of names to values: add, change or delete them.
1570
+ - `req.headers` is a map of names to values: add, change or delete them. A header sent
1571
+ more than once reads as its values joined with `, `, as `res.headers` does, and is
1572
+ still sent once per value unless the script changes it. Set an array to send one
1573
+ header per value.
1240
1574
  - Variables in what the script writes resolve afterwards, as in the file.
1241
1575
  - Anything else is a pre-request error, and nothing is sent.
1242
1576
 
@@ -1298,7 +1632,8 @@ global project's root.
1298
1632
  - A secret with no value anywhere fails the run, naming it. An empty credential is never
1299
1633
  sent.
1300
1634
  - Reports replace a secret's value with `[secret: NAME]`, wherever it appears. So does
1301
- the desktop app where it shows a request as sent.
1635
+ the desktop app where it shows a request as sent, or what a script wrote with
1636
+ `console`.
1302
1637
  - `.env` holds `NAME=value` lines. `#` starts a comment line, `export ` before a name is
1303
1638
  allowed, and a value may be quoted. It must not be committed.
1304
1639
 
@@ -1343,7 +1678,7 @@ it. Rules checked at run time fail the step, or the run, before anything is sent
1343
1678
  - It must be YAML that parses, holding a mapping, in a file ending in `.yml`.
1344
1679
  - Unknown keys are errors in: a collection's top level, a step, `settings`, `before`,
1345
1680
  `body`, `project.yml`, `tls`, an environment file, an environment's `flags`, a param
1346
- spec and `settings.yml`.
1681
+ spec, `settings.yml` and `rules.yml`.
1347
1682
 
1348
1683
  **Collections**
1349
1684
 
@@ -1360,7 +1695,8 @@ it. Rules checked at run time fail the step, or the run, before anything is sent
1360
1695
  **Steps**
1361
1696
 
1362
1697
  - A step has exactly one method key, out of `GET`, `POST`, `PUT`, `PATCH`, `DELETE`,
1363
- `HEAD` and `OPTIONS`, in capitals, and its value is a string.
1698
+ `HEAD` and `OPTIONS`, in capitals, and its value is a string. A use step and a step
1699
+ reading a connection have none.
1364
1700
  - A step has no key outside the table in §2.1.
1365
1701
  - A use step has no method key, `headers`, `body`, `settings` or `before`. `with` is used
1366
1702
  only with `use`.
@@ -1369,6 +1705,8 @@ it. Rules checked at run time fail the step, or the run, before anything is sent
1369
1705
  - `base` is only ever `false`.
1370
1706
  - `forEach` is a string, and a use step has none.
1371
1707
  - `useTests` is only ever `true`, only on a request set's step, and on one step at most.
1708
+ - A step reading a connection has no `headers`, `body`, `base` or `forEach`. A step
1709
+ opening one has no `forEach`, and a use step has no `connection` (§2.11).
1372
1710
 
1373
1711
  **Values**
1374
1712
 
@@ -1377,6 +1715,7 @@ it. Rules checked at run time fail the step, or the run, before anything is sent
1377
1715
  - A tag matches `^[A-Za-z0-9._:-]+$`.
1378
1716
  - A flag name matches `^[A-Za-z0-9_][A-Za-z0-9_.-]*$`, and a flag value is a string,
1379
1717
  number or boolean.
1718
+ - A connection name matches `^[A-Za-z0-9_][A-Za-z0-9_.-]*$`.
1380
1719
  - A `settings` value has its type in §2.3.
1381
1720
 
1382
1721
  **Bodies**
@@ -1394,9 +1733,9 @@ it. Rules checked at run time fail the step, or the run, before anything is sent
1394
1733
  - A base collection (`bases/`) has no `steps`, `setup`, `teardown` or `params`, and no
1395
1734
  `extends`.
1396
1735
  - An endpoint (`endpoints/`) has a URL that is a path starting with `/`, and no use
1397
- steps. An endpoints file has no `setup` or `teardown`.
1736
+ steps or connections. An endpoints file has no `setup` or `teardown`.
1398
1737
  - A check file's name is a JavaScript identifier; if it isn't, the file is not loaded.
1399
- - `collections/`, `requests/`, `endpoints/` and `bases/` hold files at most one directory
1738
+ - `collections/`, `requests/`, `endpoints/` and `bases/` hold files at most one folder
1400
1739
  down.
1401
1740
 
1402
1741
  **Portability (§1.2)**
@@ -1407,7 +1746,7 @@ it. Rules checked at run time fail the step, or the run, before anything is sent
1407
1746
  `Collections/`.
1408
1747
  - No two names in `collections/`, `requests/`, `endpoints/`, `bases/`, `environments/`
1409
1748
  or `checks/` differ only in case.
1410
- - No file or directory name is one Windows refuses.
1749
+ - No file or folder name is one Windows refuses.
1411
1750
 
1412
1751
  **Projects**
1413
1752
 
@@ -1416,6 +1755,9 @@ it. Rules checked at run time fail the step, or the run, before anything is sent
1416
1755
  - Each `tls.ca` file exists and holds a PEM or DER certificate.
1417
1756
  - `settings.yml` exists for `gta`; a global project's is optional. The `environmentType`
1418
1757
  a run ends up with, if any, names an environment that exists.
1758
+ - In `rules.yml`, each rule has a value of the type §1.4 gives it, a pattern compiles,
1759
+ `tests.only` lists `gta`, and `guide` is a string. What breaks a rule is a finding of `gta lint`, never an
1760
+ error: it stops nothing (§1.4).
1419
1761
 
1420
1762
  **At run time**
1421
1763
 
@@ -1430,6 +1772,7 @@ it. Rules checked at run time fail the step, or the run, before anything is sent
1430
1772
  `gta get` checks these, and the body files named without `{{variables}}`, without
1431
1773
  running anything (§1.3).
1432
1774
  - A step's `forEach` resolves to a JSON array.
1775
+ - A step reading a connection finds it open: a step before it in the run opened it.
1433
1776
  - In `{{@name}}`, `name` holds text naming a variable that exists.
1434
1777
  - A `before.script` sets `req.body` to text, and only for a `json`, `xml`, `text` or
1435
1778
  `graphql` body; `req.headers` stays a map.