@voxgig/sdkgen 4.25.0 → 4.26.1
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/package.json +1 -1
- package/project/.sdk/tm/js/test/live-scenarios.js +7 -0
- package/project/.sdk/tm/lua/Makefile +3 -1
- package/project/.sdk/tm/lua/test/option_defaults_test.lua +92 -0
- package/project/.sdk/tm/lua/utility/make_options.lua +65 -0
- package/project/.sdk/tm/ts/test/live-scenarios.ts +6 -0
- package/project/sdkgen-package.json +1 -1
package/bin/voxgig-sdkgen
CHANGED
package/package.json
CHANGED
|
@@ -50,6 +50,13 @@ async function runLiveScenarios(SDK, plan, envPrefix, liveDefaults = {}) {
|
|
|
50
50
|
excluded: control.skip ? control.reason || 'Excluded by test control' : hint.excluded,
|
|
51
51
|
run: async (ctx) => {
|
|
52
52
|
transport.enter(ctx);
|
|
53
|
+
// Every operation point is planned, not only the hinted ones; a hint
|
|
54
|
+
// only decides whether this file is generated. Absent a recipe there
|
|
55
|
+
// is no consent to call the operation, so block rather than
|
|
56
|
+
// synthesize input for it. Blocked fails assertLiveReport, so an
|
|
57
|
+
// omission surfaces instead of passing quietly.
|
|
58
|
+
if (!point.facts.live)
|
|
59
|
+
throw new LiveBlocked('No live recipe: add a live hint for this operation in the guide, or give it an explicit excluded reason');
|
|
53
60
|
if (point.contractVersion && point.contractVersion !== 1)
|
|
54
61
|
throw new live_runner_1.LiveBlocked('Unsupported operation contract version');
|
|
55
62
|
if (point.op === 'remove' || hint.cleanup) {
|
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
NATIVE ?=
|
|
12
12
|
-include $(wildcard feature/*/native.mk)
|
|
13
13
|
|
|
14
|
+
# One Lua state for the whole suite, as the other targets share one process:
|
|
15
|
+
# insulating each file re-requires the SDK and re-decodes its config per file.
|
|
14
16
|
test: build
|
|
15
|
-
busted -p _test test/
|
|
17
|
+
busted --no-auto-insulate -p _test test/
|
|
16
18
|
|
|
17
19
|
build: $(NATIVE)
|
|
18
20
|
@echo "lua: build complete$(if $(NATIVE), ($(NATIVE)),: no compile step)"
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
-- ProjectName SDK option defaults test
|
|
2
|
+
--
|
|
3
|
+
-- make_options validates the configured entity defaults once per Lua state
|
|
4
|
+
-- rather than per client: struct's lua getprop scans the parent to classify
|
|
5
|
+
-- it, which made validating n entities cost n^2 on every construction.
|
|
6
|
+
|
|
7
|
+
local sdk = require("project-name-_sdk")
|
|
8
|
+
local vs = require("utility.struct.struct")
|
|
9
|
+
local schema = require("schema")
|
|
10
|
+
local config = require("config_shared")()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
local function configured()
|
|
14
|
+
local names = {}
|
|
15
|
+
for name, _ in pairs(config.options.entity or {}) do
|
|
16
|
+
table.insert(names, name)
|
|
17
|
+
end
|
|
18
|
+
table.sort(names)
|
|
19
|
+
return names
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
local function whole(user)
|
|
24
|
+
local merged = vs.merge({ {}, vs.clone(config.options.entity or {}), vs.clone(user or {}) })
|
|
25
|
+
local out = vs.validate({ entity = merged }, { entity = schema.OPTSPEC["entity"] })
|
|
26
|
+
return out.entity
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
describe("make_options entity", function()
|
|
31
|
+
|
|
32
|
+
it("matches validating the whole entity map at once", function()
|
|
33
|
+
assert.are.same(whole(nil), sdk.test().options.entity)
|
|
34
|
+
|
|
35
|
+
local name = configured()[1]
|
|
36
|
+
if name ~= nil then
|
|
37
|
+
local user = {
|
|
38
|
+
[name] = { active = true, alias = { a = "b" } },
|
|
39
|
+
not_configured = { active = true },
|
|
40
|
+
}
|
|
41
|
+
assert.are.same(whole(user), sdk.test(nil, { entity = user }).options.entity)
|
|
42
|
+
end
|
|
43
|
+
end)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
it("keeps each client's entity options its own", function()
|
|
47
|
+
local name = configured()[1]
|
|
48
|
+
if name == nil then
|
|
49
|
+
return
|
|
50
|
+
end
|
|
51
|
+
local a = sdk.test()
|
|
52
|
+
a.options.entity[name].active = true
|
|
53
|
+
a.options.entity[name].alias.leak = true
|
|
54
|
+
local b = sdk.test()
|
|
55
|
+
assert.are.equal(false, b.options.entity[name].active)
|
|
56
|
+
assert.is_nil(b.options.entity[name].alias.leak)
|
|
57
|
+
end)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
it("validates the configured entity map at most once for many clients", function()
|
|
61
|
+
local names = configured()
|
|
62
|
+
local full = 0
|
|
63
|
+
local validate = vs.validate
|
|
64
|
+
vs.validate = function(data, spec, injdef)
|
|
65
|
+
local ent = type(data) == "table" and data.entity or nil
|
|
66
|
+
if type(ent) == "table" and 1 < #names then
|
|
67
|
+
local all = true
|
|
68
|
+
for _, name in ipairs(names) do
|
|
69
|
+
if ent[name] == nil then
|
|
70
|
+
all = false
|
|
71
|
+
break
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
if all then
|
|
75
|
+
full = full + 1
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
return validate(data, spec, injdef)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
local ok, err = pcall(function()
|
|
82
|
+
for _ = 1, 3 do
|
|
83
|
+
sdk.test()
|
|
84
|
+
end
|
|
85
|
+
end)
|
|
86
|
+
vs.validate = validate
|
|
87
|
+
|
|
88
|
+
assert.is_true(ok, tostring(err))
|
|
89
|
+
assert.is_true(full <= 1, "entity map validated " .. full .. " times for 3 clients")
|
|
90
|
+
end)
|
|
91
|
+
|
|
92
|
+
end)
|
|
@@ -48,6 +48,40 @@ local function densify_lists(v, seen)
|
|
|
48
48
|
return v
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
+
local function copy_data(v)
|
|
52
|
+
if type(v) ~= 'table' then
|
|
53
|
+
return v
|
|
54
|
+
end
|
|
55
|
+
local out = {}
|
|
56
|
+
for k, e in pairs(v) do
|
|
57
|
+
out[k] = copy_data(e)
|
|
58
|
+
end
|
|
59
|
+
return setmetatable(out, getmetatable(v))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
-- struct's lua getprop scans every key of the parent to classify it, so
|
|
63
|
+
-- validating a map of n entities costs n^2. Each entity entry validates on its
|
|
64
|
+
-- own, and the config's entries are fixed for this Lua state: validate them
|
|
65
|
+
-- once, and per client only the entries the caller passes.
|
|
66
|
+
local entity_defaults_src = nil
|
|
67
|
+
local entity_defaults = nil
|
|
68
|
+
|
|
69
|
+
local function validate_entities(entities, spec)
|
|
70
|
+
local out = vs.validate({ entity = densify_lists(entities) }, { entity = spec })
|
|
71
|
+
if type(out) == 'table' and type(out.entity) == 'table' then
|
|
72
|
+
return out.entity
|
|
73
|
+
end
|
|
74
|
+
return {}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
local function validated_entity_defaults(cfgentity, spec)
|
|
78
|
+
if entity_defaults_src ~= cfgentity then
|
|
79
|
+
entity_defaults = validate_entities(vs.clone(cfgentity), spec)
|
|
80
|
+
entity_defaults_src = cfgentity
|
|
81
|
+
end
|
|
82
|
+
return entity_defaults
|
|
83
|
+
end
|
|
84
|
+
|
|
51
85
|
local function make_options_util(ctx)
|
|
52
86
|
local options = ctx.options or {}
|
|
53
87
|
|
|
@@ -150,6 +184,22 @@ local function make_options_util(ctx)
|
|
|
150
184
|
-- singleton (see config_shared), and merge would otherwise use its nested
|
|
151
185
|
-- tables as merge TARGETS — one instance's options (server, headers, ...)
|
|
152
186
|
-- would contaminate every instance constructed after it.
|
|
187
|
+
local cfgentity = cfgopts["entity"]
|
|
188
|
+
local userentity = opts["entity"]
|
|
189
|
+
local split = type(cfgentity) == 'table' and vs.ismap(cfgentity)
|
|
190
|
+
and (userentity == nil or vs.ismap(userentity))
|
|
191
|
+
|
|
192
|
+
if split then
|
|
193
|
+
local rest = {}
|
|
194
|
+
for k, v in pairs(cfgopts) do
|
|
195
|
+
if k ~= "entity" then
|
|
196
|
+
rest[k] = v
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
cfgopts = rest
|
|
200
|
+
opts["entity"] = nil
|
|
201
|
+
end
|
|
202
|
+
|
|
153
203
|
local merged = vs.merge({ {}, vs.clone(cfgopts), opts })
|
|
154
204
|
|
|
155
205
|
-- LUA CANNOT STORE NIL, so `{ a, nil, b }` is a table holding keys 1 and 3
|
|
@@ -171,6 +221,21 @@ local function make_options_util(ctx)
|
|
|
171
221
|
end
|
|
172
222
|
opts = validated
|
|
173
223
|
|
|
224
|
+
if split then
|
|
225
|
+
local entity = copy_data(validated_entity_defaults(cfgentity, optspec["entity"]))
|
|
226
|
+
if userentity ~= nil then
|
|
227
|
+
local picked = {}
|
|
228
|
+
for name, _ in pairs(userentity) do
|
|
229
|
+
picked[name] = cfgentity[name]
|
|
230
|
+
end
|
|
231
|
+
local own = vs.merge({ {}, vs.clone(picked), userentity })
|
|
232
|
+
for name, e in pairs(validate_entities(own, optspec["entity"])) do
|
|
233
|
+
entity[name] = e
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
opts["entity"] = entity
|
|
237
|
+
end
|
|
238
|
+
|
|
174
239
|
-- Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
|
|
175
240
|
-- Every placeholder must resolve to a non-empty value: from
|
|
176
241
|
-- options.server (user), else the config default. A placeholder that
|
|
@@ -41,6 +41,12 @@ export async function runLiveScenarios(SDK: any, plan: any[], envPrefix: string,
|
|
|
41
41
|
excluded: control.skip ? control.reason || 'Excluded by test control' : hint.excluded,
|
|
42
42
|
run: async (ctx: any) => {
|
|
43
43
|
transport.enter(ctx)
|
|
44
|
+
// Every operation point is planned, not only the hinted ones; a hint
|
|
45
|
+
// only decides whether this file is generated. Absent a recipe there
|
|
46
|
+
// is no consent to call the operation, so block rather than
|
|
47
|
+
// synthesize input for it. Blocked fails assertLiveReport, so an
|
|
48
|
+
// omission surfaces instead of passing quietly.
|
|
49
|
+
if (!point.facts.live) throw new LiveBlocked('No live recipe: add a live hint for this operation in the guide, or give it an explicit excluded reason')
|
|
44
50
|
if (point.contractVersion && point.contractVersion !== 1) throw new LiveBlocked('Unsupported operation contract version')
|
|
45
51
|
if (point.op === 'remove' || hint.cleanup) {
|
|
46
52
|
const owned = recipeNeeds(hint.input).some(id => plan.some(source =>
|