@voxgig/sdkgen 4.6.0 → 4.7.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/bin/voxgig-sdkgen +1 -1
- package/dist/cmp/Feature.js +5 -0
- package/dist/cmp/Feature.js.map +1 -1
- package/dist/cmp/TestControl.d.ts +4 -0
- package/dist/cmp/TestControl.js +51 -0
- package/dist/cmp/TestControl.js.map +1 -0
- package/dist/helpers/featureSource.d.ts +3 -1
- package/dist/helpers/featureSource.js +105 -0
- package/dist/helpers/featureSource.js.map +1 -1
- package/dist/sdkgen.d.ts +3 -2
- package/dist/sdkgen.js +9 -4
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.d.ts +2 -1
- package/dist/utility.js +52 -0
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aon +58 -0
- package/package.json +1 -1
- package/project/.sdk/model/feature/secrets.aon +82 -0
- package/project/.sdk/src/cmp/cpp/Main_cpp.ts +2 -1
- package/project/.sdk/src/cmp/cpp/Test_cpp.ts +5 -1
- package/project/.sdk/src/cmp/csharp/Main_csharp.ts +2 -1
- package/project/.sdk/src/cmp/csharp/TestDirect_csharp.ts +30 -6
- package/project/.sdk/src/cmp/csharp/TestEntity_csharp.ts +29 -5
- package/project/.sdk/src/cmp/csharp/Test_csharp.ts +5 -1
- package/project/.sdk/src/cmp/dart/Main_dart.ts +2 -1
- package/project/.sdk/src/cmp/dart/TestDirect_dart.ts +23 -6
- package/project/.sdk/src/cmp/dart/TestEntity_dart.ts +28 -7
- package/project/.sdk/src/cmp/dart/Test_dart.ts +5 -1
- package/project/.sdk/src/cmp/go/Main_go.ts +2 -1
- package/project/.sdk/src/cmp/go/Test_go.ts +5 -1
- package/project/.sdk/src/cmp/java/Main_java.ts +2 -1
- package/project/.sdk/src/cmp/java/Test_java.ts +5 -1
- package/project/.sdk/src/cmp/js/Main_js.ts +2 -1
- package/project/.sdk/src/cmp/js/TestDirect_js.ts +30 -5
- package/project/.sdk/src/cmp/js/TestEntity_js.ts +35 -5
- package/project/.sdk/src/cmp/js/Test_js.ts +5 -1
- package/project/.sdk/src/cmp/js/fragment/Direct.test.fragment.js +7 -1
- package/project/.sdk/src/cmp/js/fragment/Entity.test.fragment.js +7 -1
- package/project/.sdk/src/cmp/kotlin/Main_kotlin.ts +2 -1
- package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +5 -1
- package/project/.sdk/src/cmp/lua/Main_lua.ts +2 -1
- package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +28 -4
- package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +24 -4
- package/project/.sdk/src/cmp/lua/Test_lua.ts +5 -1
- package/project/.sdk/src/cmp/ocaml/Gitignore_ocaml.ts +1 -0
- package/project/.sdk/src/cmp/perl/Main_perl.ts +2 -1
- package/project/.sdk/src/cmp/perl/TestDirect_perl.ts +25 -4
- package/project/.sdk/src/cmp/perl/TestEntity_perl.ts +24 -4
- package/project/.sdk/src/cmp/perl/Test_perl.ts +5 -1
- package/project/.sdk/src/cmp/perl/utility_perl.ts +10 -0
- package/project/.sdk/src/cmp/php/Main_php.ts +2 -1
- package/project/.sdk/src/cmp/php/TestDirect_php.ts +24 -5
- package/project/.sdk/src/cmp/php/TestEntity_php.ts +28 -4
- package/project/.sdk/src/cmp/php/Test_php.ts +5 -1
- package/project/.sdk/src/cmp/py/Main_py.ts +2 -1
- package/project/.sdk/src/cmp/py/Test_py.ts +5 -1
- package/project/.sdk/src/cmp/rb/Main_rb.ts +2 -1
- package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +24 -5
- package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +24 -4
- package/project/.sdk/src/cmp/rb/Test_rb.ts +5 -1
- package/project/.sdk/src/cmp/rb/utility_rb.ts +3 -1
- package/project/.sdk/src/cmp/rust/Main_rust.ts +2 -1
- package/project/.sdk/src/cmp/rust/TestDirect_rust.ts +31 -4
- package/project/.sdk/src/cmp/rust/TestEntity_rust.ts +37 -4
- package/project/.sdk/src/cmp/rust/Test_rust.ts +5 -1
- package/project/.sdk/src/cmp/scala/Main_scala.ts +2 -1
- package/project/.sdk/src/cmp/scala/Test_scala.ts +5 -1
- package/project/.sdk/src/cmp/ts/Config_ts.ts +50 -8
- package/project/.sdk/src/cmp/ts/Main_ts.ts +10 -2
- package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +10 -3
- package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +11 -3
- package/project/.sdk/src/cmp/ts/Test_ts.ts +5 -1
- package/project/.sdk/tm/csharp/test/Runner.cs +45 -0
- package/project/.sdk/tm/dart/test/utility.dart +39 -0
- package/project/.sdk/tm/js/test/sdk-test-control.json +19 -0
- package/project/.sdk/tm/js/test/utility.js +91 -1
- package/project/.sdk/tm/lua/test/runner.lua +40 -0
- package/project/.sdk/tm/ocaml/test/harness.ml +14 -4
- package/project/.sdk/tm/perl/t/runner.pm +30 -0
- package/project/.sdk/tm/php/test/Runner.php +36 -0
- package/project/.sdk/tm/rb/test/runner.rb +25 -0
- package/project/.sdk/tm/rust/tests/common/mod.rs +32 -0
- package/project/.sdk/tm/rust/utility/make_options.rs +1 -1
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +8 -2
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +29 -5
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +30 -21
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/Registry.ts +90 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/addr.ts +48 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/aws.ts +219 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/azuresecrets.ts +144 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/boru.ts +113 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/doppler.ts +77 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/dotenv.ts +51 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/env.ts +31 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/file.ts +54 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/gcpsecrets.ts +118 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/hashicorp.ts +159 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/http.ts +66 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/infisical.ts +107 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/memory.ts +33 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/onepassword.ts +120 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/support.ts +176 -0
- package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +15 -13
- package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +25 -19
- package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +1 -1
- package/project/.sdk/tm/ts/test/vendor/omni/index.ts +1 -1
- package/project/.sdk/tm/zig/core/utility.zig +1 -1
- package/project/sdkgen-package.json +1 -1
- package/src/cmp/Feature.ts +5 -0
- package/src/cmp/TestControl.ts +64 -0
- package/src/helpers/featureSource.ts +130 -0
- package/src/sdkgen.ts +7 -0
- package/src/utility.ts +61 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Providers.ts +0 -1221
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
cmp, each,
|
|
6
6
|
File, Content, Copy, Folder, Fragment,
|
|
7
7
|
targetFeatures,
|
|
8
|
+
TEST_CONTROL_EXCLUDE
|
|
8
9
|
} from '@voxgig/sdkgen'
|
|
9
10
|
|
|
10
11
|
|
|
@@ -51,7 +52,7 @@ const Main = cmp(async function Main(props: any) {
|
|
|
51
52
|
// ProjectName carries the SDK name into vendored template strings.
|
|
52
53
|
Copy({
|
|
53
54
|
from: 'tm/' + target.name,
|
|
54
|
-
exclude: [/src
|
|
55
|
+
exclude: [/src\//, TEST_CONTROL_EXCLUDE],
|
|
55
56
|
replace: {
|
|
56
57
|
...props.ctx$.stdrep,
|
|
57
58
|
ProjectName: model.const.Name,
|
|
@@ -9,7 +9,8 @@ import type {
|
|
|
9
9
|
ModelEntity
|
|
10
10
|
} from '@voxgig/apidef'
|
|
11
11
|
|
|
12
|
-
import { cmp, each, Folder, File, Content, entityCollection
|
|
12
|
+
import { cmp, each, Folder, File, Content, entityCollection,
|
|
13
|
+
TestControl } from '@voxgig/sdkgen'
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
import { TestEntity } from './TestEntity_scala'
|
|
@@ -31,6 +32,9 @@ const Test = cmp(function Test(props: any) {
|
|
|
31
32
|
// template, and are driven by the generated SdkEntityTestMain aggregator.
|
|
32
33
|
Folder({ name: 'sdktest' }, () => {
|
|
33
34
|
|
|
35
|
+
// Write-once: a project's edited control file survives regeneration.
|
|
36
|
+
TestControl({ target, dir: 'sdktest' })
|
|
37
|
+
|
|
34
38
|
const entities: { Name: string; entity: boolean; direct: boolean }[] = []
|
|
35
39
|
|
|
36
40
|
const entity = each(entityCollection(model))
|
|
@@ -99,10 +99,13 @@ const Config = cmp(async function Config(props: any) {
|
|
|
99
99
|
|
|
100
100
|
replace: {
|
|
101
101
|
|
|
102
|
-
'// #ImportFeatures': () =>
|
|
103
|
-
|
|
104
|
-
`
|
|
105
|
-
|
|
102
|
+
'// #ImportFeatures': () => {
|
|
103
|
+
each(feature, (f: any) => {
|
|
104
|
+
Line(`import { ${nom(f, 'Name')}Feature } from ` +
|
|
105
|
+
`'./feature/${f.name}/${nom(f, 'Name')}Feature'`)
|
|
106
|
+
})
|
|
107
|
+
pluginImports(feature)
|
|
108
|
+
},
|
|
106
109
|
|
|
107
110
|
'// #FeatureClasses': () => each(feature, (f: any) => {
|
|
108
111
|
Line(` ${f.name}: ${nom(f, 'Name')}Feature,`)
|
|
@@ -130,10 +133,13 @@ const Config = cmp(async function Config(props: any) {
|
|
|
130
133
|
|
|
131
134
|
"'HEADERS'": indent(JSON.stringify(headers, null, 2), 4).trim(),
|
|
132
135
|
|
|
133
|
-
'// #ImportFeatures': () =>
|
|
134
|
-
|
|
135
|
-
`
|
|
136
|
-
|
|
136
|
+
'// #ImportFeatures': () => {
|
|
137
|
+
each(feature, (f: any) => {
|
|
138
|
+
Line(`import { ${nom(f, 'Name')}Feature } from ` +
|
|
139
|
+
`'./feature/${f.name}/${nom(f, 'Name')}Feature'`)
|
|
140
|
+
})
|
|
141
|
+
pluginImports(feature)
|
|
142
|
+
},
|
|
137
143
|
|
|
138
144
|
// Values from configDefinition's def, not re-derived here, so the
|
|
139
145
|
// literal rep and the data rep cannot disagree on identity.
|
|
@@ -177,6 +183,42 @@ const Config = cmp(async function Config(props: any) {
|
|
|
177
183
|
})
|
|
178
184
|
|
|
179
185
|
|
|
186
|
+
|
|
187
|
+
// PLUGIN REGISTRATION IMPORTS.
|
|
188
|
+
//
|
|
189
|
+
// A sekreto provider module registers itself when it is imported, and
|
|
190
|
+
// nothing else imports it: the core surface deliberately does not reach a
|
|
191
|
+
// platform-dependent provider, and the full-set barrel — the file whose
|
|
192
|
+
// job was to reach all of them — is not vendored into an SDK at all.
|
|
193
|
+
//
|
|
194
|
+
// So without these lines an SDK generates `provider/dotenv.ts`, never
|
|
195
|
+
// imports it, and throws `unknown provider kind: dotenv` the first time a
|
|
196
|
+
// chain names it. Emitted here because Config already imports every active
|
|
197
|
+
// feature from the model, and this is the same list one level down.
|
|
198
|
+
//
|
|
199
|
+
// Import for SIDE EFFECT only — `import '...'`, no bindings. The module's
|
|
200
|
+
// top-level `register()` call is the whole point.
|
|
201
|
+
function pluginImports(feature: any) {
|
|
202
|
+
each(feature, (f: any) => {
|
|
203
|
+
each(f.plugin, (plugin: any) => {
|
|
204
|
+
// Filter on `active` HERE rather than trusting the feature object to
|
|
205
|
+
// arrive filtered. Whether a model path was read with `only_active`
|
|
206
|
+
// varies by call site, and getting it wrong in this direction emits
|
|
207
|
+
// an import for a module the trim just deleted — an SDK that does
|
|
208
|
+
// not compile, rather than one that merely carries too much.
|
|
209
|
+
if (false === plugin.active || null == plugin.active) return
|
|
210
|
+
|
|
211
|
+
for (const one of (plugin.path || [])) {
|
|
212
|
+
const path = String(one)
|
|
213
|
+
// Only a provider MODULE registers a kind; a plugin may own other
|
|
214
|
+
// files (Sigv4.ts is signing support, not a provider).
|
|
215
|
+
if (!/\/provider\/[^/]+\.ts$/.test(path)) continue
|
|
216
|
+
Line(`import '${'./' + path.replace(/^src\//, '').replace(/\.ts$/, '')}'`)
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
180
222
|
export {
|
|
181
223
|
Config
|
|
182
224
|
}
|
|
@@ -4,8 +4,10 @@ import * as Path from 'node:path'
|
|
|
4
4
|
import {
|
|
5
5
|
cmp, each, names, cmap,
|
|
6
6
|
List, File, Content, Copy, Folder, Fragment, Line, FeatureHook,
|
|
7
|
-
entityClassName, entityCollection, srcFeatureExcludes,
|
|
7
|
+
entityClassName, entityCollection, srcFeatureExcludes, pluginExcludes,
|
|
8
|
+
stationLibrary,
|
|
8
9
|
targetFeatures,
|
|
10
|
+
TEST_CONTROL_EXCLUDE
|
|
9
11
|
} from '@voxgig/sdkgen'
|
|
10
12
|
|
|
11
13
|
|
|
@@ -53,7 +55,13 @@ const Main = cmp(async function Main(props: any) {
|
|
|
53
55
|
from: 'tm/' + target.name,
|
|
54
56
|
// Root copies src/feature/<name>/ per ACTIVE feature; keep this blanket
|
|
55
57
|
// copy from restoring one that was switched off after `target add`.
|
|
56
|
-
|
|
58
|
+
// A feature's inactive plugins go too - same rule, one level
|
|
59
|
+
// deeper. See helpers/featureSource.pluginExcludes.
|
|
60
|
+
exclude: [
|
|
61
|
+
...srcFeatureExcludes(model),
|
|
62
|
+
...pluginExcludes(model),
|
|
63
|
+
TEST_CONTROL_EXCLUDE,
|
|
64
|
+
],
|
|
57
65
|
replace: {
|
|
58
66
|
...props.ctx$.stdrep,
|
|
59
67
|
}
|
|
@@ -21,7 +21,8 @@ import {
|
|
|
21
21
|
serverVariables,
|
|
22
22
|
isHttpBasicAuth,
|
|
23
23
|
jsProp,
|
|
24
|
-
jsOptProp, envName, envToken, liveStrict
|
|
24
|
+
jsOptProp, envName, envToken, liveStrict,
|
|
25
|
+
jsKey
|
|
25
26
|
} from '@voxgig/sdkgen'
|
|
26
27
|
|
|
27
28
|
|
|
@@ -49,7 +50,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
|
|
|
49
50
|
const authActive = isAuthActive(model)
|
|
50
51
|
const authBasic = authActive && isHttpBasicAuth(model)
|
|
51
52
|
const apikeyEnvEntry = authActive
|
|
52
|
-
? `\n '${PROJECTNAME}_APIKEY': '',${authBasic ? `\n '${PROJECTNAME}_SECRET': '
|
|
53
|
+
? `\n '${PROJECTNAME}_APIKEY': '',${authBasic ? `\n '${PROJECTNAME}_SECRET': '',` : ''}`
|
|
53
54
|
: ''
|
|
54
55
|
const apikeyLiveField = authActive
|
|
55
56
|
? `
|
|
@@ -61,13 +62,19 @@ const TestDirect = cmp(function TestDirect(props: any) {
|
|
|
61
62
|
// impossible to construct without values: makeOptions raises rather than
|
|
62
63
|
// request a URL with a literal `{account_id}` in it. Taken from the
|
|
63
64
|
// environment, the same way the apikey is.
|
|
65
|
+
//
|
|
66
|
+
// Keys are quoted and the env read is bracketed via jsKey/jsProp: a server
|
|
67
|
+
// variable name is spec-derived and need not be a JS identifier — the URL
|
|
68
|
+
// grammar admits a leading digit ({2fa}), and a declared-but-unreferenced
|
|
69
|
+
// variable ({edge-zone}) is not constrained at all. Bare `name:` and
|
|
70
|
+
// `env.PROJ_SERVER_EDGE-ZONE` are both syntax errors.
|
|
64
71
|
const svars = serverVariables(model)
|
|
65
72
|
const serverEnvEntry = svars
|
|
66
73
|
.map((v: any) => `\n '${serverVarEnv(PROJECTNAME, v.name)}': ${JSON.stringify(v.dflt)},`).join('')
|
|
67
74
|
const serverLiveField = 0 === svars.length ? '' : `
|
|
68
75
|
server: {${svars
|
|
69
76
|
.map((v: any) => `
|
|
70
|
-
${v.name}: env
|
|
77
|
+
${jsKey(v.name)}: ${jsProp('env', serverVarEnv(PROJECTNAME, v.name))},`).join('')}
|
|
71
78
|
},`
|
|
72
79
|
|
|
73
80
|
const opnames = Object.keys(entity.op || {})
|
|
@@ -33,7 +33,9 @@ import {
|
|
|
33
33
|
serverVarEnv,
|
|
34
34
|
serverVariables,
|
|
35
35
|
isHttpBasicAuth,
|
|
36
|
-
entityDataIdField, envName, envToken
|
|
36
|
+
entityDataIdField, envName, envToken,
|
|
37
|
+
jsKey,
|
|
38
|
+
jsProp
|
|
37
39
|
} from '@voxgig/sdkgen'
|
|
38
40
|
|
|
39
41
|
|
|
@@ -69,7 +71,7 @@ const TestEntity = cmp(function TestEntity(props: any) {
|
|
|
69
71
|
const authActive = isAuthActive(model)
|
|
70
72
|
const authBasic = authActive && isHttpBasicAuth(model)
|
|
71
73
|
const apikeyEnvEntry = authActive
|
|
72
|
-
? `\n '${PROJENVNAME}_APIKEY': '',${authBasic ? `\n '${PROJENVNAME}_SECRET': '
|
|
74
|
+
? `\n '${PROJENVNAME}_APIKEY': '',${authBasic ? `\n '${PROJENVNAME}_SECRET': '',` : ''}`
|
|
73
75
|
: ''
|
|
74
76
|
const apikeyLiveField = authActive
|
|
75
77
|
? `
|
|
@@ -81,13 +83,19 @@ const TestEntity = cmp(function TestEntity(props: any) {
|
|
|
81
83
|
// impossible to construct without values: makeOptions raises rather than
|
|
82
84
|
// request a URL with a literal `{account_id}` in it. So the live suite
|
|
83
85
|
// takes them from the environment the same way it takes the apikey.
|
|
86
|
+
//
|
|
87
|
+
// Keys are quoted and the env read is bracketed via jsKey/jsProp: a server
|
|
88
|
+
// variable name is spec-derived and need not be a JS identifier — the URL
|
|
89
|
+
// grammar admits a leading digit ({2fa}), and a declared-but-unreferenced
|
|
90
|
+
// variable ({edge-zone}) is not constrained at all. Bare `name:` and
|
|
91
|
+
// `env.PROJ_SERVER_EDGE-ZONE` are both syntax errors.
|
|
84
92
|
const svars = serverVariables(model)
|
|
85
93
|
const serverEnvEntry = svars
|
|
86
94
|
.map((v: any) => `\n '${serverVarEnv(PROJENVNAME, v.name)}': ${JSON.stringify(v.dflt)},`).join('')
|
|
87
95
|
const serverLiveField = 0 === svars.length ? '' : `
|
|
88
96
|
server: {${svars
|
|
89
97
|
.map((v: any) => `
|
|
90
|
-
${v.name}: env
|
|
98
|
+
${jsKey(v.name)}: ${jsProp('env', serverVarEnv(PROJENVNAME, v.name))},`).join('')}
|
|
91
99
|
},`
|
|
92
100
|
|
|
93
101
|
// TODO: should be a utility function
|
|
@@ -3,7 +3,8 @@ import type {
|
|
|
3
3
|
ModelEntity
|
|
4
4
|
} from '@voxgig/apidef'
|
|
5
5
|
|
|
6
|
-
import { cmp, each, Folder, entityCollection
|
|
6
|
+
import { cmp, each, Folder, entityCollection,
|
|
7
|
+
TestControl } from '@voxgig/sdkgen'
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
// import { Quick } from './Quick_ts'
|
|
@@ -19,6 +20,9 @@ const Test = cmp(function Test(props: any) {
|
|
|
19
20
|
const { target } = props
|
|
20
21
|
|
|
21
22
|
Folder({ name: 'test' }, () => {
|
|
23
|
+
|
|
24
|
+
// Write-once: a project's edited control file survives regeneration.
|
|
25
|
+
TestControl({ target, dir: 'test' })
|
|
22
26
|
// Quick({ target })
|
|
23
27
|
// TestMain({ target })
|
|
24
28
|
|
|
@@ -153,6 +153,51 @@ public static class TestRunner
|
|
|
153
153
|
return _testControl;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
// LiveClientOptions returns the extra SDK options every LIVE client is
|
|
157
|
+
// constructed with, read from sdk-test-control.json `test.client.options`.
|
|
158
|
+
//
|
|
159
|
+
// The generated live client knows two things: the base URL (from the
|
|
160
|
+
// spec) and the credential (from the environment). Everything else about
|
|
161
|
+
// how a particular API wants to be talked to - which features to switch
|
|
162
|
+
// on, and with what settings - is a property of THAT API, known to the
|
|
163
|
+
// project and to nothing in the toolchain.
|
|
164
|
+
//
|
|
165
|
+
// Merged UNDER the generated fields, so the suite's own
|
|
166
|
+
// base/apikey/server values win: this ADDS to the live client, it does
|
|
167
|
+
// not redirect it.
|
|
168
|
+
//
|
|
169
|
+
// Reserved fields are stripped HERE rather than at each merge site: the
|
|
170
|
+
// generated dictionary only names a field when the model calls for one,
|
|
171
|
+
// so a "base" in this block would face no competing value and would
|
|
172
|
+
// silently redirect the whole suite - credential included - to another
|
|
173
|
+
// host.
|
|
174
|
+
private static readonly string[] LiveReserved =
|
|
175
|
+
{ "base", "prefix", "suffix", "server", "apikey", "secret" };
|
|
176
|
+
|
|
177
|
+
public static Dictionary<string, object?> LiveClientOptions()
|
|
178
|
+
{
|
|
179
|
+
var ctrl = LoadTestControl();
|
|
180
|
+
if (!ctrl.TryGetValue("test", out var testRaw) ||
|
|
181
|
+
testRaw is not Dictionary<string, object?> test ||
|
|
182
|
+
!test.TryGetValue("client", out var clientRaw) ||
|
|
183
|
+
clientRaw is not Dictionary<string, object?> client ||
|
|
184
|
+
!client.TryGetValue("options", out var optsRaw) ||
|
|
185
|
+
optsRaw is not Dictionary<string, object?> opts)
|
|
186
|
+
{
|
|
187
|
+
return new Dictionary<string, object?>();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
var out_ = new Dictionary<string, object?>();
|
|
191
|
+
foreach (var kv in opts)
|
|
192
|
+
{
|
|
193
|
+
if (Array.IndexOf(LiveReserved, kv.Key) < 0)
|
|
194
|
+
{
|
|
195
|
+
out_[kv.Key] = kv.Value;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return out_;
|
|
199
|
+
}
|
|
200
|
+
|
|
156
201
|
// IsControlSkipped checks sdk-test-control.json for a skip entry.
|
|
157
202
|
// Returns (skip, reason).
|
|
158
203
|
public static (bool, string) IsControlSkipped(string kind, string name, string mode)
|
|
@@ -143,6 +143,45 @@ bool skipIfMissingIds(T t, Map setup, List<String> requiredKeys) {
|
|
|
143
143
|
|
|
144
144
|
// Per-test live pacing delay (ms). Read from sdk-test-control.json
|
|
145
145
|
// `test.live.delayMs`; defaults to 500ms if absent or invalid.
|
|
146
|
+
// Extra SDK options every LIVE client is constructed with, read from
|
|
147
|
+
// sdk-test-control.json `test.client.options`.
|
|
148
|
+
//
|
|
149
|
+
// The generated live client knows two things: the base URL (from the spec)
|
|
150
|
+
// and the credential (from the environment). Everything else about how a
|
|
151
|
+
// particular API wants to be talked to - which features to switch on, and
|
|
152
|
+
// with what settings - is a property of THAT API, known to the project and
|
|
153
|
+
// to nothing in the toolchain.
|
|
154
|
+
//
|
|
155
|
+
// Merged UNDER the generated fields, so the suite's own base/apikey/server
|
|
156
|
+
// values win: this ADDS to the live client, it does not redirect it.
|
|
157
|
+
//
|
|
158
|
+
// Reserved fields are stripped HERE rather than at each merge site: the
|
|
159
|
+
// generated map only names a field when the model calls for one, so a
|
|
160
|
+
// 'base' in this block would face no competing value and would silently
|
|
161
|
+
// redirect the whole suite - credential included - to another host.
|
|
162
|
+
const List<String> liveReserved = [
|
|
163
|
+
'base', 'prefix', 'suffix', 'server', 'apikey', 'secret'
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
Map<String, dynamic> liveClientOptions() {
|
|
167
|
+
final ctrl = loadTestControl();
|
|
168
|
+
final t = ctrl['test'];
|
|
169
|
+
if (t is! Map) return <String, dynamic>{};
|
|
170
|
+
final c = t['client'];
|
|
171
|
+
if (c is! Map) return <String, dynamic>{};
|
|
172
|
+
final opts = c['options'];
|
|
173
|
+
if (opts is! Map) return <String, dynamic>{};
|
|
174
|
+
|
|
175
|
+
final out = <String, dynamic>{};
|
|
176
|
+
opts.forEach((k, v) {
|
|
177
|
+
if (!liveReserved.contains(k)) {
|
|
178
|
+
out['$k'] = v;
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
146
185
|
int liveDelayMs() {
|
|
147
186
|
final ctrl = loadTestControl();
|
|
148
187
|
final t = ctrl['test'];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"_doc": "Per-SDK test control. Lists tests/operations to skip in unit and live modes; tunes per-test pacing. Edit by hand. Loaded by js/test/utility.js.",
|
|
4
|
+
"test": {
|
|
5
|
+
"skip": {
|
|
6
|
+
"live": {
|
|
7
|
+
"direct": [],
|
|
8
|
+
"entityOp": []
|
|
9
|
+
},
|
|
10
|
+
"unit": {
|
|
11
|
+
"direct": [],
|
|
12
|
+
"entityOp": []
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"live": {
|
|
16
|
+
"delayMs": 500
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* for creating test data, transformations, validations, and environment overrides.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
const Fs = require('node:fs')
|
|
9
|
+
const Path = require('node:path')
|
|
10
|
+
|
|
11
|
+
|
|
8
12
|
// Creates a new step data structure within the data model
|
|
9
13
|
function makeStepData(dm, stepname) {
|
|
10
14
|
dm.s[stepname] = {
|
|
@@ -62,11 +66,97 @@ function envOverride(m) {
|
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
|
|
69
|
+
// Loads sdk-test-control.json (cached). Returns an empty-skip object if
|
|
70
|
+
// the file is missing or unparsable so tests never crash on a bad config.
|
|
71
|
+
let _testControlCache = null
|
|
72
|
+
|
|
73
|
+
function loadTestControl() {
|
|
74
|
+
if (_testControlCache) return _testControlCache
|
|
75
|
+
const ctrlPath = Path.resolve(__dirname, '../test/sdk-test-control.json')
|
|
76
|
+
try {
|
|
77
|
+
_testControlCache = JSON.parse(Fs.readFileSync(ctrlPath, 'utf8'))
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
_testControlCache = {
|
|
81
|
+
version: 1,
|
|
82
|
+
test: { skip: { live: { direct: [], entityOp: [] }, unit: { direct: [], entityOp: [] } } }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return _testControlCache
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
// Extra SDK options every LIVE client is constructed with, read from
|
|
90
|
+
// sdk-test-control.json `test.client.options`.
|
|
91
|
+
//
|
|
92
|
+
// The generated live client knows two things: the base URL (from the spec)
|
|
93
|
+
// and the credential (from the environment). Everything else about how a
|
|
94
|
+
// particular API wants to be talked to - which features to switch on, and
|
|
95
|
+
// with what settings - is a property of THAT API, known to the project and
|
|
96
|
+
// to nothing in the toolchain. So it lives in the file the project owns and
|
|
97
|
+
// describes, where it can be read and reviewed. Secrets still come from the
|
|
98
|
+
// environment (the providers this block names read them).
|
|
99
|
+
//
|
|
100
|
+
// Merged UNDER the generated fields, so the suite's own base/apikey/server
|
|
101
|
+
// values win - this adds to the live client, it does not redirect it.
|
|
102
|
+
//
|
|
103
|
+
// That contract is enforced HERE rather than left to each merge site: the
|
|
104
|
+
// generated object only names a field when the model calls for one, so a
|
|
105
|
+
// `base` in this block would face no competing value and would silently
|
|
106
|
+
// redirect the whole suite - credential included - to another host. The
|
|
107
|
+
// reserved fields are stripped once, where the block is read, so every
|
|
108
|
+
// caller gets the same guarantee whether or not it happens to emit them.
|
|
109
|
+
const LIVE_RESERVED = ['base', 'prefix', 'suffix', 'server', 'apikey', 'secret']
|
|
110
|
+
|
|
111
|
+
function liveClientOptions() {
|
|
112
|
+
const ctrl = loadTestControl()
|
|
113
|
+
const opts = ctrl && ctrl.test && ctrl.test.client && ctrl.test.client.options
|
|
114
|
+
|
|
115
|
+
if (null == opts || 'object' !== typeof opts) {
|
|
116
|
+
return {}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const out = {}
|
|
120
|
+
for (const key of Object.keys(opts)) {
|
|
121
|
+
if (!LIVE_RESERVED.includes(key)) {
|
|
122
|
+
out[key] = opts[key]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return out
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
// Per-test live pacing delay (ms). Read from sdk-test-control.json
|
|
131
|
+
// `test.live.delayMs`; defaults to 500ms if absent or invalid.
|
|
132
|
+
function liveDelayMs() {
|
|
133
|
+
const ctrl = loadTestControl()
|
|
134
|
+
const v = ctrl && ctrl.test && ctrl.test.live && ctrl.test.live.delayMs
|
|
135
|
+
return ('number' === typeof v && v >= 0) ? v : 500
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
// afterEach hook helper for live pacing. Generated tests register this via
|
|
141
|
+
// `afterEach(liveDelay(<envVar>))`; it sleeps `liveDelayMs()` only when the
|
|
142
|
+
// SDK's *_TEST_LIVE env var is set.
|
|
143
|
+
function liveDelay(liveEnvVar) {
|
|
144
|
+
return async () => {
|
|
145
|
+
if ('TRUE' === process.env[liveEnvVar]) {
|
|
146
|
+
await new Promise(r => setTimeout(r, liveDelayMs()))
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
65
151
|
module.exports = {
|
|
66
152
|
makeStepData,
|
|
67
153
|
makeMatch,
|
|
68
154
|
makeReqdata,
|
|
69
155
|
makeValid,
|
|
70
156
|
makeCtrl,
|
|
71
|
-
envOverride
|
|
157
|
+
envOverride,
|
|
158
|
+
loadTestControl,
|
|
159
|
+
liveClientOptions,
|
|
160
|
+
liveDelayMs,
|
|
161
|
+
liveDelay
|
|
72
162
|
}
|
|
@@ -146,6 +146,46 @@ function runner.is_control_skipped(kind, name, mode)
|
|
|
146
146
|
end
|
|
147
147
|
|
|
148
148
|
|
|
149
|
+
-- Extra SDK options every LIVE client is constructed with, read from
|
|
150
|
+
-- sdk-test-control.json `test.client.options`.
|
|
151
|
+
--
|
|
152
|
+
-- The generated live client knows two things: the base URL (from the spec)
|
|
153
|
+
-- and the credential (from the environment). Everything else about how a
|
|
154
|
+
-- particular API wants to be talked to - which features to switch on, and
|
|
155
|
+
-- with what settings - is a property of THAT API, known to the project and
|
|
156
|
+
-- to nothing in the toolchain.
|
|
157
|
+
--
|
|
158
|
+
-- Merged UNDER the generated fields, so the suite's own base/apikey/server
|
|
159
|
+
-- values win: this ADDS to the live client, it does not redirect it.
|
|
160
|
+
--
|
|
161
|
+
-- Reserved fields are stripped HERE rather than at each merge site: the
|
|
162
|
+
-- generated table only names a field when the model calls for one, so a
|
|
163
|
+
-- "base" in this block would face no competing value and would silently
|
|
164
|
+
-- redirect the whole suite - credential included - to another host.
|
|
165
|
+
local LIVE_RESERVED = {
|
|
166
|
+
base = true, prefix = true, suffix = true,
|
|
167
|
+
server = true, apikey = true, secret = true,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function runner.live_client_options()
|
|
171
|
+
local ctrl = runner.load_test_control()
|
|
172
|
+
local test = ctrl.test
|
|
173
|
+
if type(test) ~= "table" then return {} end
|
|
174
|
+
local client = test.client
|
|
175
|
+
if type(client) ~= "table" then return {} end
|
|
176
|
+
local opts = client.options
|
|
177
|
+
if type(opts) ~= "table" then return {} end
|
|
178
|
+
|
|
179
|
+
local out = {}
|
|
180
|
+
for k, v in pairs(opts) do
|
|
181
|
+
if not LIVE_RESERVED[k] then
|
|
182
|
+
out[k] = v
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
return out
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
|
|
149
189
|
-- Per-test live pacing delay (ms); default 500.
|
|
150
190
|
function runner.live_delay_ms()
|
|
151
191
|
local ctrl = runner.load_test_control()
|
|
@@ -93,14 +93,24 @@ let make_client ?(server : server option) ?(mode = "test") ?(base = "http://api.
|
|
|
93
93
|
rootctx.c_op <- new_operation (jo [("name", Str "root"); ("entity", Str "_")]);
|
|
94
94
|
client.cl_rootctx <- Some rootctx;
|
|
95
95
|
List.iter (fun (name, opts) ->
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
(* `netsim` is a test-only network simulator implemented in
|
|
97
|
+
Sdk_features, not a model feature, so it never appears in the
|
|
98
|
+
generated config and has_feature rejects it. Construct it directly:
|
|
99
|
+
without this every netsim-driven test (retry backoff, timeout,
|
|
100
|
+
and the metrics/telemetry/audit error paths) silently runs against
|
|
101
|
+
an unsimulated transport and sees a plain 200. *)
|
|
102
|
+
let ftr =
|
|
103
|
+
if name = "netsim" then Some (Sdk_features.netsim_feature ())
|
|
104
|
+
else if has_feature name then Some (Sdk_config.make_feature name)
|
|
105
|
+
else None in
|
|
106
|
+
match ftr with
|
|
107
|
+
| None -> ()
|
|
108
|
+
| Some ftr ->
|
|
98
109
|
let fopts = jo [("active", Bool true)] in
|
|
99
110
|
(match opts with Map _ -> List.iter (fun k -> setp fopts k (getp opts k)) (keysof opts) | _ -> ());
|
|
100
111
|
setp (getp client.cl_options "feature") name fopts;
|
|
101
112
|
ftr.f_init rootctx fopts;
|
|
102
|
-
client.cl_features <- client.cl_features @ [ftr]
|
|
103
|
-
end) features;
|
|
113
|
+
client.cl_features <- client.cl_features @ [ftr]) features;
|
|
104
114
|
feature_hook_util rootctx "PostConstruct";
|
|
105
115
|
{ h_base = base; h_headers = hheaders; h_client = client; h_utility = utility; h_rootctx = rootctx }
|
|
106
116
|
|
|
@@ -129,6 +129,36 @@ sub is_control_skipped {
|
|
|
129
129
|
return (0, undef);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
# Extra SDK options every LIVE client is constructed with, read from
|
|
133
|
+
# sdk-test-control.json `test.client.options`.
|
|
134
|
+
#
|
|
135
|
+
# The generated live client knows two things: the base URL (from the spec)
|
|
136
|
+
# and the credential (from the environment). Everything else about how a
|
|
137
|
+
# particular API wants to be talked to - which features to switch on, and
|
|
138
|
+
# with what settings - is a property of THAT API, known to the project and
|
|
139
|
+
# to nothing in the toolchain.
|
|
140
|
+
#
|
|
141
|
+
# Merged UNDER the generated fields, so the suite's own base/apikey/server
|
|
142
|
+
# values win: this ADDS to the live client, it does not redirect it.
|
|
143
|
+
#
|
|
144
|
+
# Reserved fields are stripped HERE rather than at each merge site: the
|
|
145
|
+
# generated hash only names a field when the model calls for one, so a
|
|
146
|
+
# "base" in this block would face no competing value and would silently
|
|
147
|
+
# redirect the whole suite - credential included - to another host.
|
|
148
|
+
my %LIVE_RESERVED = map { $_ => 1 }
|
|
149
|
+
qw(base prefix suffix server apikey secret);
|
|
150
|
+
|
|
151
|
+
sub live_client_options {
|
|
152
|
+
my $ctrl = load_test_control();
|
|
153
|
+
my $opts = ProjectNameHelpers::gpath($ctrl, 'test.client.options');
|
|
154
|
+
return {} unless Voxgig::Struct::ismap($opts);
|
|
155
|
+
my %out;
|
|
156
|
+
for my $k (keys %$opts) {
|
|
157
|
+
$out{$k} = $opts->{$k} unless $LIVE_RESERVED{$k};
|
|
158
|
+
}
|
|
159
|
+
return \%out;
|
|
160
|
+
}
|
|
161
|
+
|
|
132
162
|
# Per-test live pacing delay (ms); default 500.
|
|
133
163
|
sub live_delay_ms {
|
|
134
164
|
my $ctrl = load_test_control();
|
|
@@ -138,6 +138,42 @@ class ProjectNameTestRunner
|
|
|
138
138
|
return [false, null];
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Extra SDK options every LIVE client is constructed with, read from
|
|
143
|
+
* sdk-test-control.json `test.client.options`.
|
|
144
|
+
*
|
|
145
|
+
* The generated live client knows two things: the base URL (from the
|
|
146
|
+
* spec) and the credential (from the environment). Everything else
|
|
147
|
+
* about how a particular API wants to be talked to - which features to
|
|
148
|
+
* switch on, and with what settings - is a property of THAT API, known
|
|
149
|
+
* to the project and to nothing in the toolchain.
|
|
150
|
+
*
|
|
151
|
+
* Merged UNDER the generated fields, so the suite's own
|
|
152
|
+
* base/apikey/server values win: this ADDS to the live client, it does
|
|
153
|
+
* not redirect it.
|
|
154
|
+
*
|
|
155
|
+
* Reserved fields are stripped HERE rather than at each merge site: the
|
|
156
|
+
* generated array only names a field when the model calls for one, so a
|
|
157
|
+
* "base" in this block would face no competing value and would silently
|
|
158
|
+
* redirect the whole suite - credential included - to another host.
|
|
159
|
+
*/
|
|
160
|
+
public static function live_client_options(): array
|
|
161
|
+
{
|
|
162
|
+
$ctrl = self::load_test_control();
|
|
163
|
+
$opts = $ctrl['test']['client']['options'] ?? null;
|
|
164
|
+
if (!is_array($opts)) {
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
$reserved = ['base', 'prefix', 'suffix', 'server', 'apikey', 'secret'];
|
|
168
|
+
$out = [];
|
|
169
|
+
foreach ($opts as $k => $v) {
|
|
170
|
+
if (!in_array($k, $reserved, true)) {
|
|
171
|
+
$out[$k] = $v;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return $out;
|
|
175
|
+
}
|
|
176
|
+
|
|
141
177
|
/** Per-test live pacing delay (ms); default 500. */
|
|
142
178
|
public static function live_delay_ms(): int
|
|
143
179
|
{
|
|
@@ -101,6 +101,31 @@ module ProjectNameTestRunner
|
|
|
101
101
|
[false, nil]
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
+
# Extra SDK options every LIVE client is constructed with, read from
|
|
105
|
+
# sdk-test-control.json `test.client.options`.
|
|
106
|
+
#
|
|
107
|
+
# The generated live client knows two things: the base URL (from the spec)
|
|
108
|
+
# and the credential (from the environment). Everything else about how a
|
|
109
|
+
# particular API wants to be talked to - which features to switch on, and
|
|
110
|
+
# with what settings - is a property of THAT API, known to the project and
|
|
111
|
+
# to nothing in the toolchain.
|
|
112
|
+
#
|
|
113
|
+
# Merged UNDER the generated fields, so the suite's own base/apikey/server
|
|
114
|
+
# values win: this ADDS to the live client, it does not redirect it.
|
|
115
|
+
#
|
|
116
|
+
# Reserved fields are stripped HERE rather than at each merge site: the
|
|
117
|
+
# generated hash only names a field when the model calls for one, so a
|
|
118
|
+
# "base" in this block would face no competing value and would silently
|
|
119
|
+
# redirect the whole suite - credential included - to another host.
|
|
120
|
+
LIVE_RESERVED = %w[base prefix suffix server apikey secret].freeze
|
|
121
|
+
|
|
122
|
+
def self.live_client_options
|
|
123
|
+
ctrl = load_test_control
|
|
124
|
+
opts = ctrl.dig('test', 'client', 'options')
|
|
125
|
+
return {} unless opts.is_a?(Hash)
|
|
126
|
+
opts.reject { |k, _v| LIVE_RESERVED.include?(k) }
|
|
127
|
+
end
|
|
128
|
+
|
|
104
129
|
# Per-test live pacing delay (ms); default 500.
|
|
105
130
|
def self.live_delay_ms
|
|
106
131
|
ctrl = load_test_control
|