@toa.io/extensions.introspection 1.0.0-alpha.291 → 1.0.0-alpha.293
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/package.json +16 -6
- package/transpiled/Composition.d.ts +2 -7
- package/transpiled/Composition.js +4 -15
- package/transpiled/Composition.js.map +1 -1
- package/transpiled/Factory.d.ts +1 -1
- package/transpiled/Factory.js +2 -2
- package/transpiled/Factory.js.map +1 -1
- package/transpiled/Reporter.d.ts +1 -1
- package/transpiled/Reporter.js +1 -1
- package/transpiled/Reporter.js.map +1 -1
- package/transpiled/Tenant.js +1 -1
- package/transpiled/Tenant.js.map +1 -1
- package/transpiled/UI.js +1 -1
- package/transpiled/UI.js.map +1 -1
- package/transpiled/index.d.ts +1 -3
- package/transpiled/index.js +0 -2
- package/transpiled/index.js.map +1 -1
- package/transpiled/sample.js +1 -1
- package/transpiled/sample.js.map +1 -1
- package/ui/dist/_app/immutable/chunks/B2kPZfJh.js +1 -0
- package/ui/dist/_app/immutable/chunks/{BM_8gmPc.js → B6dxxHIS.js} +1 -1
- package/ui/dist/_app/immutable/chunks/BQGufqas.js +6 -0
- package/ui/dist/_app/immutable/chunks/{BPmTOjUD.js → CMT3qkok.js} +1 -1
- package/ui/dist/_app/immutable/chunks/{ClEi7LqQ.js → CRq6qlvN.js} +1 -1
- package/ui/dist/_app/immutable/chunks/{BNP4DAhr.js → Cdb6j7Bw.js} +1 -1
- package/ui/dist/_app/immutable/chunks/DFP66uri.js +1 -0
- package/ui/dist/_app/immutable/chunks/{D58WJWRP.js → Qo6KLWZ1.js} +1 -1
- package/ui/dist/_app/immutable/chunks/jxky52kD.js +1 -0
- package/ui/dist/_app/immutable/entry/{app.C__edJT8.js → app.MKQlb7LU.js} +2 -2
- package/ui/dist/_app/immutable/entry/start.RcPu9STL.js +1 -0
- package/ui/dist/_app/immutable/nodes/{0.DqHzPxwp.js → 0.Gc5w_8Fb.js} +1 -1
- package/ui/dist/_app/immutable/nodes/{1.CxKiCrAx.js → 1.CZzwHnag.js} +1 -1
- package/ui/dist/_app/immutable/nodes/{2.DiR-utWy.js → 2.DRxH9AhI.js} +1 -1
- package/ui/dist/_app/immutable/nodes/3.DXaS4-Wc.js +1 -0
- package/ui/dist/_app/immutable/nodes/4.C424lXpX.js +1 -0
- package/ui/dist/_app/immutable/nodes/{5.BjL9k_iK.js → 5.BlPcxfID.js} +1 -1
- package/ui/dist/_app/version.json +1 -1
- package/ui/dist/index.html +12 -12
- package/CHANGELOG.md +0 -35
- package/components/introspection.edges/tsconfig.tsbuildinfo +0 -1
- package/components/introspection.nodes/tsconfig.tsbuildinfo +0 -1
- package/cucumber.mjs +0 -8
- package/features/calls.feature +0 -123
- package/features/nodes.feature +0 -70
- package/features/samples.feature +0 -75
- package/features/shutdown.feature +0 -37
- package/features/site/_app/immutable/asset.js +0 -1
- package/features/site/favicon.ico +0 -0
- package/features/site/index.html +0 -10
- package/features/steps/Map.ts +0 -184
- package/features/steps/UI.ts +0 -98
- package/features/steps/components/probe.discreet/manifest.toa.yaml +0 -16
- package/features/steps/components/probe.discreet/operations/ping.js +0 -3
- package/features/steps/components/probe.quiet/manifest.toa.yaml +0 -15
- package/features/steps/components/probe.quiet/operations/ping.js +0 -3
- package/features/steps/components/probe.source/manifest.toa.yaml +0 -40
- package/features/steps/components/probe.source/operations/crash.js +0 -3
- package/features/steps/components/probe.source/operations/fail.js +0 -8
- package/features/steps/components/probe.source/operations/relay.js +0 -3
- package/features/steps/components/probe.target/manifest.toa.yaml +0 -30
- package/features/steps/components/probe.target/operations/compute.js +0 -3
- package/features/steps/components/probe.target/operations/count.js +0 -5
- package/features/steps/config.ts +0 -27
- package/features/steps/tsconfig.json +0 -9
- package/features/ui.feature +0 -93
- package/schemas/annotation.cos.yaml +0 -31
- package/schemas/declaration.cos.yaml +0 -5
- package/source/Composition.ts +0 -60
- package/source/Explorer.ts +0 -16
- package/source/Factory.ts +0 -152
- package/source/Reporter.ts +0 -209
- package/source/Tenant.ts +0 -46
- package/source/UI.ts +0 -213
- package/source/annotation.ts +0 -87
- package/source/const.ts +0 -46
- package/source/describe.ts +0 -75
- package/source/extension.ts +0 -49
- package/source/index.ts +0 -7
- package/source/keys.ts +0 -0
- package/source/manifest.ts +0 -18
- package/source/model.ts +0 -83
- package/source/sample.ts +0 -47
- package/source/schemas.ts +0 -12
- package/transpiled/annotation.d.ts +0 -40
- package/transpiled/annotation.js +0 -39
- package/transpiled/annotation.js.map +0 -1
- package/transpiled/const.d.ts +0 -30
- package/transpiled/const.js +0 -35
- package/transpiled/const.js.map +0 -1
- package/transpiled/extension.d.ts +0 -14
- package/transpiled/extension.js +0 -42
- package/transpiled/extension.js.map +0 -1
- package/transpiled/manifest.d.ts +0 -9
- package/transpiled/manifest.js +0 -16
- package/transpiled/manifest.js.map +0 -1
- package/transpiled/schemas.d.ts +0 -4
- package/transpiled/schemas.js +0 -7
- package/transpiled/schemas.js.map +0 -1
- package/tsconfig.json +0 -13
- package/tsconfig.tsbuildinfo +0 -1
- package/ui/dist/_app/immutable/chunks/B29ORAfk.js +0 -1
- package/ui/dist/_app/immutable/chunks/B2Uho8Qa.js +0 -1
- package/ui/dist/_app/immutable/chunks/DJkgMsju.js +0 -6
- package/ui/dist/_app/immutable/chunks/xuD-gQB1.js +0 -1
- package/ui/dist/_app/immutable/entry/start.1kW6Dv7l.js +0 -1
- package/ui/dist/_app/immutable/nodes/3.C4ocqYxG.js +0 -1
- package/ui/dist/_app/immutable/nodes/4.BxBsqIXE.js +0 -1
package/features/nodes.feature
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
Feature: Component descriptions
|
|
2
|
-
|
|
3
|
-
The static half of the map: what each component is, taken from its manifest
|
|
4
|
-
when the composition starts. This is where the event graph lives — which events
|
|
5
|
-
a component publishes, and which event each of its receivers takes — since all
|
|
6
|
-
of it is declared, and none of it needs traffic to be true.
|
|
7
|
-
|
|
8
|
-
Scenario: Describing operations
|
|
9
|
-
Then the map contains a node:
|
|
10
|
-
"""yaml
|
|
11
|
-
namespace: probe
|
|
12
|
-
component: target
|
|
13
|
-
operations:
|
|
14
|
-
- endpoint: compute
|
|
15
|
-
type: computation
|
|
16
|
-
scope: none
|
|
17
|
-
input:
|
|
18
|
-
type: object
|
|
19
|
-
properties:
|
|
20
|
-
a: { type: number }
|
|
21
|
-
b: { type: number }
|
|
22
|
-
output:
|
|
23
|
-
type: number
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
Scenario: Describing the entity
|
|
27
|
-
Then the map contains a node:
|
|
28
|
-
"""yaml
|
|
29
|
-
namespace: probe
|
|
30
|
-
component: target
|
|
31
|
-
entity:
|
|
32
|
-
storage: "@toa.io/storages.null"
|
|
33
|
-
schema:
|
|
34
|
-
properties:
|
|
35
|
-
counted:
|
|
36
|
-
type: integer
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
Scenario: Describing events
|
|
40
|
-
Then the map contains a node:
|
|
41
|
-
"""yaml
|
|
42
|
-
namespace: probe
|
|
43
|
-
component: source
|
|
44
|
-
events:
|
|
45
|
-
- label: created
|
|
46
|
-
- label: updated
|
|
47
|
-
- label: deleted
|
|
48
|
-
- label: sync
|
|
49
|
-
"""
|
|
50
|
-
|
|
51
|
-
Scenario: Describing receivers
|
|
52
|
-
Then the map contains a node:
|
|
53
|
-
"""yaml
|
|
54
|
-
namespace: probe
|
|
55
|
-
component: target
|
|
56
|
-
receivers:
|
|
57
|
-
- label: probe.source.created
|
|
58
|
-
source: probe.source
|
|
59
|
-
event: created
|
|
60
|
-
operation: count
|
|
61
|
-
conditioned: false
|
|
62
|
-
adaptive: false
|
|
63
|
-
"""
|
|
64
|
-
|
|
65
|
-
Scenario: An opted out component is not described
|
|
66
|
-
Then the map contains no node:
|
|
67
|
-
"""yaml
|
|
68
|
-
namespace: probe
|
|
69
|
-
component: quiet
|
|
70
|
-
"""
|
package/features/samples.feature
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
Feature: Payload samples
|
|
2
|
-
|
|
3
|
-
Samples are real user data, so they are captured only when the context and the
|
|
4
|
-
component both allow it. Either level can veto.
|
|
5
|
-
|
|
6
|
-
Scenario: Nothing is captured by default
|
|
7
|
-
When the `probe.target.compute` is called with:
|
|
8
|
-
"""yaml
|
|
9
|
-
input:
|
|
10
|
-
a: 4
|
|
11
|
-
b: 5
|
|
12
|
-
"""
|
|
13
|
-
Then the map contains an edge:
|
|
14
|
-
"""yaml
|
|
15
|
-
dst:
|
|
16
|
-
namespace: probe
|
|
17
|
-
component: target
|
|
18
|
-
operation: compute
|
|
19
|
-
"""
|
|
20
|
-
And the map contains no edge:
|
|
21
|
-
"""yaml
|
|
22
|
-
dst:
|
|
23
|
-
namespace: probe
|
|
24
|
-
component: target
|
|
25
|
-
operation: compute
|
|
26
|
-
sample:
|
|
27
|
-
input:
|
|
28
|
-
a: 4
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
@samples
|
|
32
|
-
Scenario: The context enables capturing
|
|
33
|
-
When the `probe.target.compute` is called with:
|
|
34
|
-
"""yaml
|
|
35
|
-
input:
|
|
36
|
-
a: 4
|
|
37
|
-
b: 5
|
|
38
|
-
"""
|
|
39
|
-
Then the map contains an edge:
|
|
40
|
-
"""yaml
|
|
41
|
-
dst:
|
|
42
|
-
namespace: probe
|
|
43
|
-
component: target
|
|
44
|
-
operation: compute
|
|
45
|
-
sample:
|
|
46
|
-
outcome: ok
|
|
47
|
-
input:
|
|
48
|
-
a: 4
|
|
49
|
-
b: 5
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
@samples
|
|
53
|
-
Scenario: A component vetoes capturing for itself
|
|
54
|
-
When the `probe.discreet.ping` is called with:
|
|
55
|
-
"""yaml
|
|
56
|
-
input:
|
|
57
|
-
a: 7
|
|
58
|
-
"""
|
|
59
|
-
Then the map contains an edge:
|
|
60
|
-
"""yaml
|
|
61
|
-
dst:
|
|
62
|
-
namespace: probe
|
|
63
|
-
component: discreet
|
|
64
|
-
operation: ping
|
|
65
|
-
"""
|
|
66
|
-
And the map contains no edge:
|
|
67
|
-
"""yaml
|
|
68
|
-
dst:
|
|
69
|
-
namespace: probe
|
|
70
|
-
component: discreet
|
|
71
|
-
operation: ping
|
|
72
|
-
sample:
|
|
73
|
-
input:
|
|
74
|
-
a: 7
|
|
75
|
-
"""
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
@shutdown
|
|
2
|
-
Feature: Graceful shutdown
|
|
3
|
-
|
|
4
|
-
A component reports on a timer, so whatever it observed since the last report
|
|
5
|
-
would be lost on a deploy. Stopping flushes it instead: the collector drains
|
|
6
|
-
its buffer while its remotes are still connected.
|
|
7
|
-
|
|
8
|
-
This suite runs with a flush period long enough that the timer cannot fire,
|
|
9
|
-
so nothing here reaches the map unless the shutdown does it.
|
|
10
|
-
|
|
11
|
-
Scenario: Observations are flushed on shutdown
|
|
12
|
-
When the `probe.source.relay` is called with:
|
|
13
|
-
"""yaml
|
|
14
|
-
input:
|
|
15
|
-
a: 2
|
|
16
|
-
b: 3
|
|
17
|
-
"""
|
|
18
|
-
And the components are stopped
|
|
19
|
-
Then the map contains an edge:
|
|
20
|
-
"""yaml
|
|
21
|
-
src:
|
|
22
|
-
namespace: probe
|
|
23
|
-
component: source
|
|
24
|
-
operation: relay
|
|
25
|
-
dst:
|
|
26
|
-
namespace: probe
|
|
27
|
-
component: target
|
|
28
|
-
operation: compute
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
Scenario: Component descriptions are flushed on shutdown
|
|
32
|
-
When the components are stopped
|
|
33
|
-
Then the map contains a node:
|
|
34
|
-
"""yaml
|
|
35
|
-
namespace: probe
|
|
36
|
-
component: target
|
|
37
|
-
"""
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const hashed = true
|
|
Binary file
|
package/features/site/index.html
DELETED
package/features/steps/Map.ts
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert'
|
|
2
|
-
import { resolve } from 'node:path'
|
|
3
|
-
import { readdirSync } from 'node:fs'
|
|
4
|
-
import { setTimeout } from 'node:timers/promises'
|
|
5
|
-
import { MongoClient } from 'mongodb'
|
|
6
|
-
import tsflow from 'cucumber-tsflow'
|
|
7
|
-
|
|
8
|
-
import { load as parse } from 'js-yaml'
|
|
9
|
-
import { match } from '@toa.io/generic'
|
|
10
|
-
import * as boot from '@toa.io/boot'
|
|
11
|
-
import { Locator } from '@toa.io/core'
|
|
12
|
-
import * as stage from '@toa.io/userland/stage'
|
|
13
|
-
import { Factory } from '../../source/index.js'
|
|
14
|
-
import type { Component, Connector } from '@toa.io/core'
|
|
15
|
-
import type { Request } from '@toa.io/core/types'
|
|
16
|
-
|
|
17
|
-
const { after, before, binding, then, when } = tsflow
|
|
18
|
-
|
|
19
|
-
@binding()
|
|
20
|
-
export class Map {
|
|
21
|
-
private service: Connector | null = null
|
|
22
|
-
private composition: Connector | null = null
|
|
23
|
-
private remotes: Record<string, Component> = {}
|
|
24
|
-
private unidentified: Component | null = null
|
|
25
|
-
|
|
26
|
-
@when('the `{word}` is called with:')
|
|
27
|
-
public async call(endpoint: string, yaml: string): Promise<void> {
|
|
28
|
-
const request = parse(yaml) as Request
|
|
29
|
-
const [operation, component, namespace = 'default'] = endpoint.split('.').reverse()
|
|
30
|
-
|
|
31
|
-
// an operation may throw, and that is exactly what one of the scenarios is about
|
|
32
|
-
await this.invoke(`${namespace}.${component}`, operation, request).catch(
|
|
33
|
-
() => undefined
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* The stage names itself as the caller, as every entry point does; a call that names
|
|
39
|
-
* nobody is made past it, straight through a remote of its own.
|
|
40
|
-
*/
|
|
41
|
-
@when('the `{word}` is called by an unidentified caller with:')
|
|
42
|
-
public async callUnidentified(endpoint: string, yaml: string): Promise<void> {
|
|
43
|
-
const request = parse(yaml) as Request
|
|
44
|
-
const [operation, component, namespace = 'default'] = endpoint.split('.').reverse()
|
|
45
|
-
|
|
46
|
-
this.unidentified ??= await boot.remote(new Locator(component, namespace))
|
|
47
|
-
|
|
48
|
-
await this.unidentified.connect()
|
|
49
|
-
await this.unidentified.invoke(operation, request).catch(() => undefined)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
@then('the map contains a node:')
|
|
53
|
-
public async node(yaml: string): Promise<void> {
|
|
54
|
-
await this.eventually('introspection.nodes', parse(yaml) as object)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
@then('the map contains an edge:')
|
|
58
|
-
public async edge(yaml: string): Promise<void> {
|
|
59
|
-
await this.eventually('introspection.edges', parse(yaml) as object)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
@then('the map contains no node:')
|
|
63
|
-
public async noNode(yaml: string): Promise<void> {
|
|
64
|
-
await this.never('introspection.nodes', parse(yaml) as object)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
@then('the map contains no edge:')
|
|
68
|
-
public async noEdge(yaml: string): Promise<void> {
|
|
69
|
-
await this.never('introspection.edges', parse(yaml) as object)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
@when('the components are stopped')
|
|
73
|
-
public async stop(): Promise<void> {
|
|
74
|
-
await this.composition?.disconnect()
|
|
75
|
-
|
|
76
|
-
this.composition = null
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
@before('not @ui')
|
|
80
|
-
public async run(): Promise<void> {
|
|
81
|
-
await clean()
|
|
82
|
-
|
|
83
|
-
this.service = (await new Factory(boot.host()).service())!
|
|
84
|
-
|
|
85
|
-
await this.service.connect()
|
|
86
|
-
|
|
87
|
-
// booted here rather than through the stage, so that a scenario can stop it on its own
|
|
88
|
-
this.composition = await boot.composition(components())
|
|
89
|
-
|
|
90
|
-
await this.composition.connect()
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
@after('not @ui')
|
|
94
|
-
public async shutdown(): Promise<void> {
|
|
95
|
-
this.remotes = {}
|
|
96
|
-
|
|
97
|
-
await this.unidentified?.disconnect()
|
|
98
|
-
await this.composition?.disconnect()
|
|
99
|
-
await stage.shutdown()
|
|
100
|
-
await this.service?.disconnect()
|
|
101
|
-
|
|
102
|
-
this.composition = null
|
|
103
|
-
this.service = null
|
|
104
|
-
this.unidentified = null
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
private async invoke(
|
|
108
|
-
id: string,
|
|
109
|
-
operation: string,
|
|
110
|
-
request: Request
|
|
111
|
-
): Promise<unknown> {
|
|
112
|
-
this.remotes[id] ??= await stage.remote(id)
|
|
113
|
-
|
|
114
|
-
return await this.remotes[id].invoke(operation, request)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/** The map is eventually consistent: the collector buffers, flushes, and the task is queued. */
|
|
118
|
-
private async eventually(id: string, expected: object): Promise<void> {
|
|
119
|
-
const deadline = Date.now() + DEADLINE
|
|
120
|
-
|
|
121
|
-
let records: any[] = []
|
|
122
|
-
|
|
123
|
-
while (Date.now() < deadline) {
|
|
124
|
-
records = await this.list(id)
|
|
125
|
-
|
|
126
|
-
if (records.some((record) => match(record, expected))) return
|
|
127
|
-
|
|
128
|
-
await setTimeout(POLL)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
assert.fail(
|
|
132
|
-
`No record in '${id}' matches\n${JSON.stringify(expected, null, 2)}\n\n` +
|
|
133
|
-
`Present:\n${JSON.stringify(records, null, 2)}`
|
|
134
|
-
)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** Absence has to outlive a flush, otherwise it only proves the map is slow. */
|
|
138
|
-
private async never(id: string, expected: object): Promise<void> {
|
|
139
|
-
await setTimeout(SETTLE)
|
|
140
|
-
|
|
141
|
-
const records = await this.list(id)
|
|
142
|
-
const found = records.find((record) => match(record, expected))
|
|
143
|
-
|
|
144
|
-
assert.equal(
|
|
145
|
-
found,
|
|
146
|
-
undefined,
|
|
147
|
-
`Unexpected record in '${id}':\n${JSON.stringify(found, null, 2)}`
|
|
148
|
-
)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
private async list(id: string): Promise<any[]> {
|
|
152
|
-
return (await this.invoke(id, 'enumerate', { query: { limit: 100 } })) as any[]
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async function clean(): Promise<void> {
|
|
157
|
-
const client = new MongoClient(URL)
|
|
158
|
-
|
|
159
|
-
await client.connect()
|
|
160
|
-
|
|
161
|
-
const db = client.db(DB)
|
|
162
|
-
|
|
163
|
-
await Promise.all([
|
|
164
|
-
db.collection('introspection_nodes').deleteMany({}),
|
|
165
|
-
db.collection('introspection_edges').deleteMany({})
|
|
166
|
-
])
|
|
167
|
-
|
|
168
|
-
await client.close()
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function components(): string[] {
|
|
172
|
-
const entries = readdirSync(ROOT, { withFileTypes: true })
|
|
173
|
-
|
|
174
|
-
return entries
|
|
175
|
-
.filter((entry) => entry.isDirectory())
|
|
176
|
-
.map((entry) => resolve(ROOT, entry.name))
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const ROOT = resolve(import.meta.dirname, 'components')
|
|
180
|
-
const URL = 'mongodb://developer:secret@localhost:31020'
|
|
181
|
-
const DB = 'toa-dev'
|
|
182
|
-
const DEADLINE = 20_000
|
|
183
|
-
const POLL = 250
|
|
184
|
-
const SETTLE = 4_000
|
package/features/steps/UI.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert'
|
|
2
|
-
import * as http from 'node:http'
|
|
3
|
-
import { resolve } from 'node:path'
|
|
4
|
-
import tsflow from 'cucumber-tsflow'
|
|
5
|
-
|
|
6
|
-
import { UI } from '../../source/UI.js'
|
|
7
|
-
|
|
8
|
-
const { after, binding, given, then, when } = tsflow
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Not `UI_PORT`, which a deployment serves the page on: an application built on Toa is served
|
|
12
|
-
* on this machine too, and its own explorer holds that one. See CONTRIBUTING.md.
|
|
13
|
-
*/
|
|
14
|
-
const PORT = 31002
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* The scenarios are about the server, not about the page: it is pointed at a fixture
|
|
18
|
-
* directory, so that running them never needs a UI build.
|
|
19
|
-
*/
|
|
20
|
-
@binding()
|
|
21
|
-
export class Site {
|
|
22
|
-
private server: UI | null = null
|
|
23
|
-
private response: Response | null = null
|
|
24
|
-
|
|
25
|
-
@given('the UI is published')
|
|
26
|
-
public async publish(): Promise<void> {
|
|
27
|
-
this.server = new UI(PORT, resolve(import.meta.dirname, '..', 'site'))
|
|
28
|
-
|
|
29
|
-
await this.server.connect()
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
@when('{string} is requested')
|
|
33
|
-
public async request(path: string): Promise<void> {
|
|
34
|
-
this.response = await get(path)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
@then('the status is {int}')
|
|
38
|
-
public statusIs(status: number): void {
|
|
39
|
-
assert.equal(this.response?.status, status)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
@then('the body contains {string}')
|
|
43
|
-
public bodyContains(text: string): void {
|
|
44
|
-
assert.ok(
|
|
45
|
-
this.response?.body.includes(text),
|
|
46
|
-
`Expected the body to contain '${text}', got '${this.response?.body}'`
|
|
47
|
-
)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
@then('the body is empty')
|
|
51
|
-
public bodyIsEmpty(): void {
|
|
52
|
-
assert.equal(this.response?.body, '')
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
@then('the {string} header is {string}')
|
|
56
|
-
public headerIs(name: string, value: string): void {
|
|
57
|
-
assert.equal(this.response?.headers[name], value)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
@after()
|
|
61
|
-
public async shutdown(): Promise<void> {
|
|
62
|
-
await this.server?.disconnect()
|
|
63
|
-
|
|
64
|
-
this.server = null
|
|
65
|
-
this.response = null
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Raw `http`, not `fetch`: the URL parser normalizes `%2e%2e` away, and one of the
|
|
71
|
-
* scenarios is about exactly that segment reaching the server.
|
|
72
|
-
*/
|
|
73
|
-
async function get(path: string): Promise<Response> {
|
|
74
|
-
return await new Promise((resolve, reject) => {
|
|
75
|
-
// `agent: false` — a keep-alive socket outlives the scenario that opened it
|
|
76
|
-
const request = http.get({ port: PORT, path, agent: false }, (response) => {
|
|
77
|
-
let body = ''
|
|
78
|
-
|
|
79
|
-
response.setEncoding('utf8')
|
|
80
|
-
response.on('data', (chunk: string) => (body += chunk))
|
|
81
|
-
response.on('end', () => {
|
|
82
|
-
resolve({
|
|
83
|
-
status: response.statusCode!,
|
|
84
|
-
headers: response.headers as Record<string, string>,
|
|
85
|
-
body
|
|
86
|
-
})
|
|
87
|
-
})
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
request.on('error', reject)
|
|
91
|
-
})
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
interface Response {
|
|
95
|
-
status: number
|
|
96
|
-
headers: Record<string, string>
|
|
97
|
-
body: string
|
|
98
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
namespace: probe
|
|
2
|
-
name: source
|
|
3
|
-
|
|
4
|
-
entity:
|
|
5
|
-
associated: true
|
|
6
|
-
storage: null
|
|
7
|
-
properties:
|
|
8
|
-
value:
|
|
9
|
-
type: integer
|
|
10
|
-
secret:
|
|
11
|
-
type: string
|
|
12
|
-
blank:
|
|
13
|
-
value: 0
|
|
14
|
-
|
|
15
|
-
operations:
|
|
16
|
-
transit:
|
|
17
|
-
concurrency: retry
|
|
18
|
-
input:
|
|
19
|
-
properties:
|
|
20
|
-
value:
|
|
21
|
-
type: string
|
|
22
|
-
default: .
|
|
23
|
-
secret:
|
|
24
|
-
type: string
|
|
25
|
-
default: .
|
|
26
|
-
type: object
|
|
27
|
-
relay:
|
|
28
|
-
input:
|
|
29
|
-
properties:
|
|
30
|
-
a:
|
|
31
|
-
type: number
|
|
32
|
-
b:
|
|
33
|
-
type: number
|
|
34
|
-
type: object
|
|
35
|
-
output:
|
|
36
|
-
type: number
|
|
37
|
-
crash: {}
|
|
38
|
-
fail:
|
|
39
|
-
errors:
|
|
40
|
-
- NOPE
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
namespace: probe
|
|
2
|
-
name: target
|
|
3
|
-
|
|
4
|
-
entity:
|
|
5
|
-
storage: null
|
|
6
|
-
properties:
|
|
7
|
-
counted:
|
|
8
|
-
type: integer
|
|
9
|
-
blank:
|
|
10
|
-
counted: 0
|
|
11
|
-
|
|
12
|
-
operations:
|
|
13
|
-
compute:
|
|
14
|
-
input:
|
|
15
|
-
properties:
|
|
16
|
-
a:
|
|
17
|
-
type: number
|
|
18
|
-
b:
|
|
19
|
-
type: number
|
|
20
|
-
type: object
|
|
21
|
-
output:
|
|
22
|
-
type: number
|
|
23
|
-
count:
|
|
24
|
-
concurrency: retry
|
|
25
|
-
input:
|
|
26
|
-
type: object
|
|
27
|
-
additionalProperties: true
|
|
28
|
-
|
|
29
|
-
receivers:
|
|
30
|
-
probe.source.created: count
|
package/features/steps/config.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { setDefaultTimeout } from '@cucumber/cucumber'
|
|
2
|
-
import { environment } from '@toa.io/generic'
|
|
3
|
-
|
|
4
|
-
environment.set('TOA_DEV', '1')
|
|
5
|
-
|
|
6
|
-
// a reply is checked against what the operation declares, so the suite runs Toa under the
|
|
7
|
-
// contract it asks applications to keep
|
|
8
|
-
if (!environment.has('TOA_ENV')) environment.set('TOA_ENV', 'local')
|
|
9
|
-
|
|
10
|
-
/*
|
|
11
|
-
* The extension factory reads the environment once, and the bootloader caches
|
|
12
|
-
* factories per process — so the context level of the annotation is fixed for
|
|
13
|
-
* a whole run. `npm run features` therefore makes a pass per configuration.
|
|
14
|
-
*/
|
|
15
|
-
environment.set(
|
|
16
|
-
'TOA_INTROSPECTION',
|
|
17
|
-
JSON.stringify({
|
|
18
|
-
samples: environment.get('TOA_INTROSPECTION_SAMPLES') === '1',
|
|
19
|
-
interval: Number(environment.get('TOA_INTROSPECTION_INTERVAL') ?? 1),
|
|
20
|
-
threshold: 64,
|
|
21
|
-
|
|
22
|
-
// `ui.feature` starts its own server; the explorer must not take the port first
|
|
23
|
-
ui: false
|
|
24
|
-
})
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
setDefaultTimeout(60 * 1000)
|