@griffin-app/griffin-ts 0.1.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/ASSERTIONS_QUICK_REF.md +161 -0
- package/README.md +297 -0
- package/dist/assertions.d.ts +136 -0
- package/dist/assertions.d.ts.map +1 -0
- package/dist/assertions.js +230 -0
- package/dist/assertions.js.map +1 -0
- package/dist/builder.d.ts +168 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +165 -0
- package/dist/builder.js.map +1 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +6 -0
- package/dist/constants.js.map +1 -0
- package/dist/example-sequential.d.ts +11 -0
- package/dist/example-sequential.d.ts.map +1 -0
- package/dist/example-sequential.js +160 -0
- package/dist/example-sequential.js.map +1 -0
- package/dist/example.d.ts +9 -0
- package/dist/example.d.ts.map +1 -0
- package/dist/example.js +36 -0
- package/dist/example.js.map +1 -0
- package/dist/frequency.d.ts +15 -0
- package/dist/frequency.d.ts.map +1 -0
- package/dist/frequency.js +34 -0
- package/dist/frequency.js.map +1 -0
- package/dist/http-methods.d.ts +6 -0
- package/dist/http-methods.d.ts.map +1 -0
- package/dist/http-methods.js +9 -0
- package/dist/http-methods.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -0
- package/dist/response-formats.d.ts +4 -0
- package/dist/response-formats.d.ts.map +1 -0
- package/dist/response-formats.js +7 -0
- package/dist/response-formats.js.map +1 -0
- package/dist/schema-exports.d.ts +6 -0
- package/dist/schema-exports.d.ts.map +1 -0
- package/dist/schema-exports.js +43 -0
- package/dist/schema-exports.js.map +1 -0
- package/dist/schema.d.ts +311 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +214 -0
- package/dist/schema.js.map +1 -0
- package/dist/secrets.d.ts +56 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +74 -0
- package/dist/secrets.js.map +1 -0
- package/dist/sequential-builder.d.ts +127 -0
- package/dist/sequential-builder.d.ts.map +1 -0
- package/dist/sequential-builder.js +128 -0
- package/dist/sequential-builder.js.map +1 -0
- package/dist/shared.d.ts +8 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +36 -0
- package/dist/shared.js.map +1 -0
- package/dist/target.d.ts +33 -0
- package/dist/target.d.ts.map +1 -0
- package/dist/target.js +53 -0
- package/dist/target.js.map +1 -0
- package/dist/type-exports.d.ts +6 -0
- package/dist/type-exports.d.ts.map +1 -0
- package/dist/type-exports.js +7 -0
- package/dist/type-exports.js.map +1 -0
- package/dist/wait.d.ts +9 -0
- package/dist/wait.d.ts.map +1 -0
- package/dist/wait.js +8 -0
- package/dist/wait.js.map +1 -0
- package/package.json +43 -0
- package/src/assertions.ts +327 -0
- package/src/builder.ts +336 -0
- package/src/constants.ts +5 -0
- package/src/example-sequential.ts +191 -0
- package/src/example.ts +55 -0
- package/src/frequency.ts +38 -0
- package/src/http-methods.ts +5 -0
- package/src/index.ts +70 -0
- package/src/response-formats.ts +3 -0
- package/src/schema-exports.ts +43 -0
- package/src/schema.ts +289 -0
- package/src/secrets.ts +112 -0
- package/src/sequential-builder.ts +254 -0
- package/src/shared.ts +46 -0
- package/src/target.ts +55 -0
- package/src/type-exports.ts +20 -0
- package/src/wait.ts +4 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Assertion DSL Quick Reference
|
|
2
|
+
|
|
3
|
+
## Basic Structure
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
.assert((state) => [
|
|
7
|
+
Assert(state["node_name"].accessor["path"]).predicate(),
|
|
8
|
+
])
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Accessors
|
|
12
|
+
|
|
13
|
+
| Accessor | Description | Example |
|
|
14
|
+
| ---------- | -------------------- | --------------------------------------- |
|
|
15
|
+
| `.body` | Response body (JSON) | `state["node"].body["data"]["id"]` |
|
|
16
|
+
| `.headers` | Response headers | `state["node"].headers["content-type"]` |
|
|
17
|
+
| `.status` | HTTP status code | `state["node"].status` |
|
|
18
|
+
|
|
19
|
+
## Array Access
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
state["node"].body["items"].at(0)["name"]; // First item
|
|
23
|
+
state["node"].body["matrix"].at(2).at(3); // 2D array
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Unary Predicates
|
|
27
|
+
|
|
28
|
+
| Predicate | Description | Negation |
|
|
29
|
+
| -------------- | ---------------------------- | -------------------------------- |
|
|
30
|
+
| `.isNull()` | Value is null | `.not.isNull()` / `.isDefined()` |
|
|
31
|
+
| `.isDefined()` | Value is not null | `.not.isDefined()` / `.isNull()` |
|
|
32
|
+
| `.isTrue()` | Value is true | `.not.isTrue()` |
|
|
33
|
+
| `.isFalse()` | Value is false | `.not.isFalse()` |
|
|
34
|
+
| `.isEmpty()` | String/array/object is empty | `.not.isEmpty()` |
|
|
35
|
+
|
|
36
|
+
## Binary Predicates - Equality
|
|
37
|
+
|
|
38
|
+
| Predicate | Description | Example |
|
|
39
|
+
| ---------------- | --------------------- | ------------------------------------- |
|
|
40
|
+
| `.equals(x)` | Value equals x | `Assert(status).equals(200)` |
|
|
41
|
+
| `.not.equals(x)` | Value doesn't equal x | `Assert(error).not.equals("timeout")` |
|
|
42
|
+
|
|
43
|
+
## Binary Predicates - Comparison
|
|
44
|
+
|
|
45
|
+
| Predicate | Description | Example |
|
|
46
|
+
| ------------------------ | ----------- | ------------------------------------ |
|
|
47
|
+
| `.greaterThan(x)` | Value > x | `Assert(count).greaterThan(0)` |
|
|
48
|
+
| `.greaterThanOrEqual(x)` | Value >= x | `Assert(age).greaterThanOrEqual(18)` |
|
|
49
|
+
| `.lessThan(x)` | Value < x | `Assert(time_ms).lessThan(500)` |
|
|
50
|
+
| `.lessThanOrEqual(x)` | Value <= x | `Assert(score).lessThanOrEqual(100)` |
|
|
51
|
+
|
|
52
|
+
Negations reverse the operator:
|
|
53
|
+
|
|
54
|
+
- `.not.greaterThan(100)` → value <= 100
|
|
55
|
+
- `.not.lessThan(0)` → value >= 0
|
|
56
|
+
|
|
57
|
+
## Binary Predicates - Strings
|
|
58
|
+
|
|
59
|
+
| Predicate | Description | Example |
|
|
60
|
+
| -------------------- | --------------------------- | ---------------------------------------- |
|
|
61
|
+
| `.contains(x)` | String contains x | `Assert(type).contains("json")` |
|
|
62
|
+
| `.not.contains(x)` | String doesn't contain x | `Assert(url).not.contains("staging")` |
|
|
63
|
+
| `.startsWith(x)` | String starts with x | `Assert(path).startsWith("/api/")` |
|
|
64
|
+
| `.not.startsWith(x)` | String doesn't start with x | `Assert(version).not.startsWith("v1.")` |
|
|
65
|
+
| `.endsWith(x)` | String ends with x | `Assert(email).endsWith("@example.com")` |
|
|
66
|
+
| `.not.endsWith(x)` | String doesn't end with x | `Assert(file).not.endsWith(".tmp")` |
|
|
67
|
+
|
|
68
|
+
## Common Patterns
|
|
69
|
+
|
|
70
|
+
### Status Checks
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
Assert(state["node"].status).equals(200);
|
|
74
|
+
Assert(state["node"].status).lessThan(400); // Any success
|
|
75
|
+
Assert(state["node"].status).not.equals(500); // Not server error
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Existence Checks
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
Assert(state["node"].body["id"]).not.isNull();
|
|
82
|
+
Assert(state["node"].body["data"]).isDefined();
|
|
83
|
+
Assert(state["node"].body["items"]).not.isEmpty();
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Value Validation
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
Assert(state["node"].body["email"]).equals("test@example.com");
|
|
90
|
+
Assert(state["node"].body["age"]).greaterThanOrEqual(18);
|
|
91
|
+
Assert(state["node"].body["score"]).lessThan(100);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### String Matching
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
Assert(state["node"].headers["content-type"]).contains("application/json");
|
|
98
|
+
Assert(state["node"].body["version"]).startsWith("v2.");
|
|
99
|
+
Assert(state["node"].body["email"]).endsWith("@example.com");
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Boolean Checks
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
Assert(state["node"].body["active"]).isTrue();
|
|
106
|
+
Assert(state["node"].body["deleted"]).isFalse();
|
|
107
|
+
Assert(state["node"].body["verified"]).not.isFalse();
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Array Elements
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
Assert(state["node"].body["items"].at(0)["name"]).equals("First");
|
|
114
|
+
Assert(state["node"].body["users"].at(2)["active"]).isTrue();
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Multi-Node Example
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
createTestBuilder({ name: "user-flow" })
|
|
121
|
+
.request("create", { method: POST /* ... */ })
|
|
122
|
+
.request("verify", { method: GET /* ... */ })
|
|
123
|
+
.assert((state) => [
|
|
124
|
+
// Check create response
|
|
125
|
+
Assert(state["create"].status).equals(201),
|
|
126
|
+
Assert(state["create"].body["id"]).not.isNull(),
|
|
127
|
+
|
|
128
|
+
// Check verify response
|
|
129
|
+
Assert(state["verify"].status).equals(200),
|
|
130
|
+
Assert(state["verify"].body["email"]).equals("test@example.com"),
|
|
131
|
+
]);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Type Safety
|
|
135
|
+
|
|
136
|
+
TypeScript provides autocomplete and errors:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
.request("user_create", { /* ... */ })
|
|
140
|
+
.assert((state) => [
|
|
141
|
+
state["user_create"] // ✓ Autocomplete shows this node
|
|
142
|
+
state["typo"] // ❌ TypeScript error
|
|
143
|
+
|
|
144
|
+
state["user_create"].body // ✓ Valid accessor
|
|
145
|
+
state["user_create"].invalid // ❌ TypeScript error
|
|
146
|
+
])
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Cheat Sheet
|
|
150
|
+
|
|
151
|
+
| Task | Code |
|
|
152
|
+
| ----------------- | -------------------------------------------------------- |
|
|
153
|
+
| Status is 200 | `Assert(state["n"].status).equals(200)` |
|
|
154
|
+
| Body field exists | `Assert(state["n"].body["x"]).not.isNull()` |
|
|
155
|
+
| String contains | `Assert(state["n"].body["msg"]).contains("ok")` |
|
|
156
|
+
| Number in range | `Assert(state["n"].body["age"]).greaterThanOrEqual(18)` |
|
|
157
|
+
| Boolean true | `Assert(state["n"].body["active"]).isTrue()` |
|
|
158
|
+
| Header has value | `Assert(state["n"].headers["x-id"]).not.isEmpty()` |
|
|
159
|
+
| Array not empty | `Assert(state["n"].body["items"]).not.isEmpty()` |
|
|
160
|
+
| First array item | `Assert(state["n"].body["items"].at(0)["name"])...` |
|
|
161
|
+
| Nested object | `Assert(state["n"].body["user"]["profile"]["email"])...` |
|
package/README.md
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# griffin Test System
|
|
2
|
+
|
|
3
|
+
The griffin Test System provides a TypeScript DSL for defining API tests. Tests are written in TypeScript and output JSON test plans that can be executed by the plan executor.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- TypeScript DSL for defining API checks
|
|
8
|
+
- Chainable API for building test plans
|
|
9
|
+
- Support for endpoints, waits, assertions, and edges
|
|
10
|
+
- Outputs JSON test plans for execution
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install griffin
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install
|
|
22
|
+
npm run build
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
Create test files in `__griffin__` directories. When executed, they output JSON test plans.
|
|
28
|
+
|
|
29
|
+
griffin provides two builder APIs:
|
|
30
|
+
|
|
31
|
+
- **`createTestBuilder`**: Simple sequential tests (recommended for most use cases)
|
|
32
|
+
- **`createGraphBuilder`**: Complex graphs with parallel execution and branching
|
|
33
|
+
|
|
34
|
+
### Sequential Builder (Recommended for Simple Tests)
|
|
35
|
+
|
|
36
|
+
The sequential builder automatically connects steps in order - no need to manage edges manually.
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { GET, createTestBuilder, JSON, Frequency } from "griffin-ts";
|
|
40
|
+
|
|
41
|
+
const plan = createTestBuilder({
|
|
42
|
+
name: "health-check",
|
|
43
|
+
frequency: Frequency.every(1).minute(),
|
|
44
|
+
})
|
|
45
|
+
.request({
|
|
46
|
+
method: GET,
|
|
47
|
+
response_format: JSON,
|
|
48
|
+
path: "/health",
|
|
49
|
+
})
|
|
50
|
+
.assert([
|
|
51
|
+
{ type: "status", expected: 200 },
|
|
52
|
+
{ type: "body", expected: { status: "ok" } },
|
|
53
|
+
])
|
|
54
|
+
.build();
|
|
55
|
+
|
|
56
|
+
export default plan;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### Sequential Example with Waits
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import {
|
|
63
|
+
GET,
|
|
64
|
+
POST,
|
|
65
|
+
createTestBuilder,
|
|
66
|
+
JSON,
|
|
67
|
+
Frequency,
|
|
68
|
+
Wait,
|
|
69
|
+
} from "griffin-ts";
|
|
70
|
+
|
|
71
|
+
const plan = createTestBuilder({
|
|
72
|
+
name: "create-and-verify-user",
|
|
73
|
+
frequency: Frequency.every(5).minute(),
|
|
74
|
+
})
|
|
75
|
+
.request({
|
|
76
|
+
method: POST,
|
|
77
|
+
response_format: JSON,
|
|
78
|
+
path: "/api/v1/users",
|
|
79
|
+
body: { name: "Test User", email: "test@example.com" },
|
|
80
|
+
})
|
|
81
|
+
.assert([{ type: "status", expected: 201 }])
|
|
82
|
+
.wait(Wait.seconds(2))
|
|
83
|
+
.request({
|
|
84
|
+
method: GET,
|
|
85
|
+
response_format: JSON,
|
|
86
|
+
path: "/api/v1/users/test@example.com",
|
|
87
|
+
})
|
|
88
|
+
.assert([
|
|
89
|
+
{ type: "status", expected: 200 },
|
|
90
|
+
{ type: "body.name", expected: "Test User" },
|
|
91
|
+
])
|
|
92
|
+
.build();
|
|
93
|
+
|
|
94
|
+
export default plan;
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Graph Builder (For Complex Workflows)
|
|
98
|
+
|
|
99
|
+
The graph builder gives you full control over the test graph, enabling parallel execution and complex branching.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import {
|
|
103
|
+
GET,
|
|
104
|
+
POST,
|
|
105
|
+
createGraphBuilder,
|
|
106
|
+
Endpoint,
|
|
107
|
+
Assertion,
|
|
108
|
+
WaitNode,
|
|
109
|
+
JSON,
|
|
110
|
+
START,
|
|
111
|
+
END,
|
|
112
|
+
Frequency,
|
|
113
|
+
Wait,
|
|
114
|
+
} from "griffin-ts";
|
|
115
|
+
|
|
116
|
+
const plan = createGraphBuilder({
|
|
117
|
+
name: "foo-bar-check",
|
|
118
|
+
frequency: Frequency.every(1).minute(),
|
|
119
|
+
})
|
|
120
|
+
.addNode(
|
|
121
|
+
"create_foo",
|
|
122
|
+
Endpoint({
|
|
123
|
+
method: POST,
|
|
124
|
+
response_format: JSON,
|
|
125
|
+
path: "/api/v1/foo",
|
|
126
|
+
body: { name: "test", value: 42 },
|
|
127
|
+
}),
|
|
128
|
+
)
|
|
129
|
+
.addNode(
|
|
130
|
+
"get_foo",
|
|
131
|
+
Endpoint({
|
|
132
|
+
method: GET,
|
|
133
|
+
response_format: JSON,
|
|
134
|
+
path: "/api/v1/foo/1",
|
|
135
|
+
}),
|
|
136
|
+
)
|
|
137
|
+
.addNode("wait_between", WaitNode(Wait.seconds(2)))
|
|
138
|
+
.addNode("check_status", Assertion([{ type: "status", expected: 200 }]))
|
|
139
|
+
.addEdge(START, "create_foo")
|
|
140
|
+
.addEdge("create_foo", "wait_between")
|
|
141
|
+
.addEdge("wait_between", "get_foo")
|
|
142
|
+
.addEdge("get_foo", "check_status")
|
|
143
|
+
.addEdge("check_status", END)
|
|
144
|
+
.build();
|
|
145
|
+
|
|
146
|
+
export default plan;
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Using Secrets
|
|
150
|
+
|
|
151
|
+
griffin supports secure secret management for API keys, tokens, and other credentials. Secrets are referenced in your test plans and resolved at runtime by the configured secret providers.
|
|
152
|
+
|
|
153
|
+
#### With Sequential Builder
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { GET, createTestBuilder, JSON, Frequency, secret } from "griffin-ts";
|
|
157
|
+
|
|
158
|
+
const plan = createTestBuilder({
|
|
159
|
+
name: "authenticated-check",
|
|
160
|
+
frequency: Frequency.every(5).minute(),
|
|
161
|
+
})
|
|
162
|
+
.request({
|
|
163
|
+
method: GET,
|
|
164
|
+
response_format: JSON,
|
|
165
|
+
path: "/api/protected",
|
|
166
|
+
headers: {
|
|
167
|
+
// Use environment variable
|
|
168
|
+
"X-API-Key": secret("env:API_KEY"),
|
|
169
|
+
// Use AWS Secrets Manager
|
|
170
|
+
Authorization: secret("aws:prod/api-token"),
|
|
171
|
+
// Extract field from JSON secret
|
|
172
|
+
"X-Custom-Header": secret("aws:prod/config", { field: "customHeader" }),
|
|
173
|
+
},
|
|
174
|
+
body: {
|
|
175
|
+
// Secrets can also be used in request bodies
|
|
176
|
+
apiKey: secret("env:API_KEY"),
|
|
177
|
+
},
|
|
178
|
+
})
|
|
179
|
+
.assert([{ type: "status", expected: 200 }])
|
|
180
|
+
.build();
|
|
181
|
+
|
|
182
|
+
export default plan;
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
#### With Graph Builder
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import {
|
|
189
|
+
GET,
|
|
190
|
+
createGraphBuilder,
|
|
191
|
+
Endpoint,
|
|
192
|
+
Assertion,
|
|
193
|
+
JSON,
|
|
194
|
+
START,
|
|
195
|
+
END,
|
|
196
|
+
Frequency,
|
|
197
|
+
secret,
|
|
198
|
+
} from "griffin-ts";
|
|
199
|
+
|
|
200
|
+
const plan = createGraphBuilder({
|
|
201
|
+
name: "authenticated-check",
|
|
202
|
+
frequency: Frequency.every(5).minute(),
|
|
203
|
+
})
|
|
204
|
+
.addNode(
|
|
205
|
+
"authenticated_request",
|
|
206
|
+
Endpoint({
|
|
207
|
+
method: GET,
|
|
208
|
+
response_format: JSON,
|
|
209
|
+
path: "/api/protected",
|
|
210
|
+
headers: {
|
|
211
|
+
"X-API-Key": secret("env:API_KEY"),
|
|
212
|
+
Authorization: secret("aws:prod/api-token"),
|
|
213
|
+
},
|
|
214
|
+
}),
|
|
215
|
+
)
|
|
216
|
+
.addNode("verify", Assertion([{ type: "status", expected: 200 }]))
|
|
217
|
+
.addEdge(START, "authenticated_request")
|
|
218
|
+
.addEdge("authenticated_request", "verify")
|
|
219
|
+
.addEdge("verify", END)
|
|
220
|
+
.build();
|
|
221
|
+
|
|
222
|
+
export default plan;
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### Secret Providers
|
|
226
|
+
|
|
227
|
+
**Environment Variables** (always available):
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
secret("env:VARIABLE_NAME");
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**AWS Secrets Manager** (requires AWS configuration):
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
secret("aws:secret-name");
|
|
237
|
+
secret("aws:secret-name", { field: "key" }); // Extract field from JSON secret
|
|
238
|
+
secret("aws:secret-name", { version: "AWSPREVIOUS" }); // Pin to specific version
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**HashiCorp Vault** (requires Vault configuration):
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
secret("vault:secret/data/path");
|
|
245
|
+
secret("vault:secret/data/path", { field: "key" });
|
|
246
|
+
secret("vault:secret/data/path", { version: "2" });
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
See the [griffin-runner CONFIG.md](../griffin-runner/CONFIG.md) for configuration details.
|
|
250
|
+
|
|
251
|
+
### API Reference
|
|
252
|
+
|
|
253
|
+
#### Sequential Builder Methods
|
|
254
|
+
|
|
255
|
+
- **`.request(config)`**: Add an HTTP endpoint request
|
|
256
|
+
- **`.wait(duration)`**: Add a delay (use `Wait.seconds(n)` or `Wait.minutes(n)`)
|
|
257
|
+
- **`.assert(assertions)`**: Add assertions to validate responses
|
|
258
|
+
- **`.build()`**: Generate the final test plan
|
|
259
|
+
|
|
260
|
+
#### Graph Builder Methods
|
|
261
|
+
|
|
262
|
+
- **`.addNode(name, node)`**: Add a node to the graph using `Endpoint()`, `WaitNode()`, or `Assertion()`
|
|
263
|
+
- **`.addEdge(from, to)`**: Connect two nodes (use `START` and `END` constants for entry/exit)
|
|
264
|
+
- **`.build()`**: Generate the final test plan (validates all nodes are connected)
|
|
265
|
+
|
|
266
|
+
#### General
|
|
267
|
+
|
|
268
|
+
- **Frequency**: Use `Frequency.every(n).minute()`, `.hour()`, or `.day()` (note the parentheses)
|
|
269
|
+
- **Secrets**: Use `secret("provider:path")` to reference secrets that are resolved at runtime
|
|
270
|
+
- **Assertions**: Currently in development - assertion functions are stored but not yet evaluated during execution
|
|
271
|
+
|
|
272
|
+
## Output
|
|
273
|
+
|
|
274
|
+
The test system outputs a JSON test plan to stdout when `plan.create()` is called. This JSON is consumed by the plan executor. Example output:
|
|
275
|
+
|
|
276
|
+
```json
|
|
277
|
+
{
|
|
278
|
+
"name": "health-check",
|
|
279
|
+
"frequency": {
|
|
280
|
+
"every": 1,
|
|
281
|
+
"unit": "minute"
|
|
282
|
+
},
|
|
283
|
+
"nodes": [
|
|
284
|
+
{
|
|
285
|
+
"id": "health",
|
|
286
|
+
"type": "endpoint",
|
|
287
|
+
"method": "GET",
|
|
288
|
+
"path": "/health",
|
|
289
|
+
"response_format": "JSON"
|
|
290
|
+
}
|
|
291
|
+
],
|
|
292
|
+
"edges": [
|
|
293
|
+
{ "from": "__START__", "to": "health" },
|
|
294
|
+
{ "from": "health", "to": "__END__" }
|
|
295
|
+
]
|
|
296
|
+
}
|
|
297
|
+
```
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rich assertion DSL for constructing type-safe test assertions with JSONPath support.
|
|
3
|
+
*
|
|
4
|
+
* This module provides:
|
|
5
|
+
* - StateProxy: Tracks node access patterns and converts them to JSONPath
|
|
6
|
+
* - Assert: Builder for creating assertions with fluent API
|
|
7
|
+
* - Support for unary and binary predicates with negation
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Identifies which node and which part of its result we're asserting on
|
|
11
|
+
*/
|
|
12
|
+
export interface PathDescriptor {
|
|
13
|
+
nodeId: string;
|
|
14
|
+
accessor: "body" | "headers" | "status";
|
|
15
|
+
path: string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Unary predicates that check a property without comparing to a value
|
|
19
|
+
*/
|
|
20
|
+
export declare enum UnaryPredicate {
|
|
21
|
+
IS_NULL = "IS_NULL",
|
|
22
|
+
IS_NOT_NULL = "IS_NOT_NULL",
|
|
23
|
+
IS_TRUE = "IS_TRUE",
|
|
24
|
+
IS_FALSE = "IS_FALSE",
|
|
25
|
+
IS_EMPTY = "IS_EMPTY",
|
|
26
|
+
IS_NOT_EMPTY = "IS_NOT_EMPTY"
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Binary predicate operators that compare against an expected value
|
|
30
|
+
*/
|
|
31
|
+
export declare enum BinaryPredicateOperator {
|
|
32
|
+
EQUAL = "EQUAL",
|
|
33
|
+
NOT_EQUAL = "NOT_EQUAL",
|
|
34
|
+
GREATER_THAN = "GREATER_THAN",
|
|
35
|
+
LESS_THAN = "LESS_THAN",
|
|
36
|
+
GREATER_THAN_OR_EQUAL = "GREATER_THAN_OR_EQUAL",
|
|
37
|
+
LESS_THAN_OR_EQUAL = "LESS_THAN_OR_EQUAL",
|
|
38
|
+
CONTAINS = "CONTAINS",
|
|
39
|
+
NOT_CONTAINS = "NOT_CONTAINS",
|
|
40
|
+
STARTS_WITH = "STARTS_WITH",
|
|
41
|
+
NOT_STARTS_WITH = "NOT_STARTS_WITH",
|
|
42
|
+
ENDS_WITH = "ENDS_WITH",
|
|
43
|
+
NOT_ENDS_WITH = "NOT_ENDS_WITH"
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Binary predicate with operator and expected value
|
|
47
|
+
*/
|
|
48
|
+
export interface BinaryPredicate {
|
|
49
|
+
operator: BinaryPredicateOperator;
|
|
50
|
+
expected: unknown;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Serialized assertion ready for execution
|
|
54
|
+
*/
|
|
55
|
+
export interface SerializedAssertion {
|
|
56
|
+
nodeId: string;
|
|
57
|
+
accessor: "body" | "headers" | "status";
|
|
58
|
+
path: string[];
|
|
59
|
+
predicate: UnaryPredicate | BinaryPredicate;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Symbol used to store path metadata in proxy objects
|
|
63
|
+
*/
|
|
64
|
+
declare const PATH_SYMBOL: unique symbol;
|
|
65
|
+
/**
|
|
66
|
+
* Internal interface for proxy objects that carry path information
|
|
67
|
+
*/
|
|
68
|
+
interface ProxyWithPath {
|
|
69
|
+
[PATH_SYMBOL]: PathDescriptor;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Proxy for accessing nested properties within a node result accessor (body, headers, status)
|
|
73
|
+
* Note: The 'at' method is available at runtime but typed as NestedProxy for simplicity
|
|
74
|
+
*/
|
|
75
|
+
export type NestedProxy = ProxyWithPath & {
|
|
76
|
+
[key: string]: NestedProxy;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Proxy for a node result with body, headers, and status accessors
|
|
80
|
+
*/
|
|
81
|
+
export type NodeResultProxy = {
|
|
82
|
+
body: NestedProxy;
|
|
83
|
+
headers: NestedProxy;
|
|
84
|
+
status: NestedProxy;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* State proxy that maps node names to their result proxies
|
|
88
|
+
*/
|
|
89
|
+
export type StateProxy<NodeNames extends string = string> = {
|
|
90
|
+
[K in NodeNames]: NodeResultProxy;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Creates a state proxy for the given node names
|
|
94
|
+
*/
|
|
95
|
+
export declare function createStateProxy<NodeNames extends string>(nodeNames: NodeNames[]): StateProxy<NodeNames>;
|
|
96
|
+
export declare class AssertBuilder {
|
|
97
|
+
private descriptor;
|
|
98
|
+
private negated;
|
|
99
|
+
constructor(descriptor: PathDescriptor);
|
|
100
|
+
/**
|
|
101
|
+
* Negation modifier - flips the meaning of the subsequent predicate
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* Assert(state["node"].body["id"]).not.isNull() // IS_NOT_NULL
|
|
105
|
+
* Assert(state["node"].body["name"]).not.equals("") // NOT_EQUAL
|
|
106
|
+
*/
|
|
107
|
+
get not(): this;
|
|
108
|
+
isNull(): SerializedAssertion;
|
|
109
|
+
isDefined(): SerializedAssertion;
|
|
110
|
+
isTrue(): SerializedAssertion;
|
|
111
|
+
isFalse(): SerializedAssertion;
|
|
112
|
+
isEmpty(): SerializedAssertion;
|
|
113
|
+
equals(expected: unknown): SerializedAssertion;
|
|
114
|
+
greaterThan(expected: number): SerializedAssertion;
|
|
115
|
+
lessThan(expected: number): SerializedAssertion;
|
|
116
|
+
greaterThanOrEqual(expected: number): SerializedAssertion;
|
|
117
|
+
lessThanOrEqual(expected: number): SerializedAssertion;
|
|
118
|
+
contains(expected: string): SerializedAssertion;
|
|
119
|
+
startsWith(expected: string): SerializedAssertion;
|
|
120
|
+
endsWith(expected: string): SerializedAssertion;
|
|
121
|
+
private createAssertion;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Creates an assertion builder from a state proxy reference
|
|
125
|
+
*
|
|
126
|
+
* @param proxyRef - A reference obtained from the state proxy (e.g., state["node"].body["id"])
|
|
127
|
+
* @returns An AssertBuilder for constructing the assertion
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* Assert(state["create_user"].body["data"]["id"]).not.isNull()
|
|
131
|
+
* Assert(state["create_user"].status).equals(201)
|
|
132
|
+
* Assert(state["create_user"].headers["content-type"]).contains("application/json")
|
|
133
|
+
*/
|
|
134
|
+
export declare function Assert(proxyRef: NestedProxy): AssertBuilder;
|
|
135
|
+
export {};
|
|
136
|
+
//# sourceMappingURL=assertions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assertions.d.ts","sourceRoot":"","sources":["../src/assertions.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;IACxC,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED;;GAEG;AACH,oBAAY,cAAc;IACxB,OAAO,YAAY;IACnB,WAAW,gBAAgB;IAC3B,OAAO,YAAY;IACnB,QAAQ,aAAa;IACrB,QAAQ,aAAa;IACrB,YAAY,iBAAiB;CAC9B;AAED;;GAEG;AACH,oBAAY,uBAAuB;IACjC,KAAK,UAAU;IACf,SAAS,cAAc;IACvB,YAAY,iBAAiB;IAC7B,SAAS,cAAc;IACvB,qBAAqB,0BAA0B;IAC/C,kBAAkB,uBAAuB;IACzC,QAAQ,aAAa;IACrB,YAAY,iBAAiB;IAC7B,WAAW,gBAAgB;IAC3B,eAAe,oBAAoB;IACnC,SAAS,cAAc;IACvB,aAAa,kBAAkB;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,uBAAuB,CAAC;IAClC,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;IACxC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,EAAE,cAAc,GAAG,eAAe,CAAC;CAC7C;AAMD;;GAEG;AACH,QAAA,MAAM,WAAW,eAAqB,CAAC;AAEvC;;GAEG;AACH,UAAU,aAAa;IACrB,CAAC,WAAW,CAAC,EAAE,cAAc,CAAC;CAC/B;AAED;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG;IACxC,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAAC;CAC5B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,WAAW,CAAC;IACrB,MAAM,EAAE,WAAW,CAAC;CACrB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,UAAU,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM,IAAI;KACzD,CAAC,IAAI,SAAS,GAAG,eAAe;CAClC,CAAC;AAuCF;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,SAAS,MAAM,EACvD,SAAS,EAAE,SAAS,EAAE,GACrB,UAAU,CAAC,SAAS,CAAC,CAavB;AAMD,qBAAa,aAAa;IAGZ,OAAO,CAAC,UAAU;IAF9B,OAAO,CAAC,OAAO,CAAS;gBAEJ,UAAU,EAAE,cAAc;IAE9C;;;;;;OAMG;IACH,IAAI,GAAG,IAAI,IAAI,CAId;IAID,MAAM,IAAI,mBAAmB;IAM7B,SAAS,IAAI,mBAAmB;IAMhC,MAAM,IAAI,mBAAmB;IAM7B,OAAO,IAAI,mBAAmB;IAM9B,OAAO,IAAI,mBAAmB;IAQ9B,MAAM,CAAC,QAAQ,EAAE,OAAO,GAAG,mBAAmB;IAS9C,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB;IASlD,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB;IAS/C,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB;IASzD,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB;IAStD,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB;IAS/C,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB;IASjD,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB;IAS/C,OAAO,CAAC,eAAe;CAUxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,MAAM,CAAC,QAAQ,EAAE,WAAW,GAAG,aAAa,CAQ3D"}
|