@voxgig/sdkgen 0.44.0 → 1.0.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.
Files changed (160) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/ReadmeEntity.js +9 -153
  3. package/dist/cmp/ReadmeEntity.js.map +1 -1
  4. package/dist/cmp/ReadmeIntro.js +9 -14
  5. package/dist/cmp/ReadmeIntro.js.map +1 -1
  6. package/dist/cmp/ReadmeModel.js +6 -4
  7. package/dist/cmp/ReadmeModel.js.map +1 -1
  8. package/dist/cmp/ReadmeOptions.js +9 -61
  9. package/dist/cmp/ReadmeOptions.js.map +1 -1
  10. package/dist/cmp/ReadmeRef.js +10 -1328
  11. package/dist/cmp/ReadmeRef.js.map +1 -1
  12. package/dist/sdkgen.d.ts +2 -2
  13. package/dist/sdkgen.js +2 -1
  14. package/dist/sdkgen.js.map +1 -1
  15. package/dist/utility.d.ts +2 -1
  16. package/dist/utility.js +9 -0
  17. package/dist/utility.js.map +1 -1
  18. package/package.json +3 -3
  19. package/project/.sdk/src/cmp/go/Config_go.ts +9 -4
  20. package/project/.sdk/src/cmp/go/Entity_go.ts +2 -2
  21. package/project/.sdk/src/cmp/go/Main_go.ts +8 -4
  22. package/project/.sdk/src/cmp/go/Package_go.ts +2 -2
  23. package/project/.sdk/src/cmp/go/ReadmeEntity_go.ts +138 -0
  24. package/project/.sdk/src/cmp/go/ReadmeExplanation_go.ts +2 -2
  25. package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +8 -5
  26. package/project/.sdk/src/cmp/go/ReadmeInstall_go.ts +2 -2
  27. package/project/.sdk/src/cmp/go/ReadmeIntro_go.ts +18 -0
  28. package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +8 -5
  29. package/project/.sdk/src/cmp/go/ReadmeOptions_go.ts +58 -0
  30. package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +13 -9
  31. package/project/.sdk/src/cmp/go/ReadmeRef_go.ts +354 -0
  32. package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +8 -6
  33. package/project/.sdk/src/cmp/go/ReadmeTopTest_go.ts +2 -2
  34. package/project/.sdk/src/cmp/go/TestDirect_go.ts +222 -41
  35. package/project/.sdk/src/cmp/go/TestEntity_go.ts +142 -60
  36. package/project/.sdk/src/cmp/go/Test_go.ts +2 -2
  37. package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +21 -4
  38. package/project/.sdk/src/cmp/js/Config_js.ts +18 -0
  39. package/project/.sdk/src/cmp/js/ReadmeEntity_js.ts +138 -0
  40. package/project/.sdk/src/cmp/js/ReadmeHowto_js.ts +11 -6
  41. package/project/.sdk/src/cmp/js/ReadmeIntro_js.ts +18 -0
  42. package/project/.sdk/src/cmp/js/ReadmeModel_js.ts +6 -3
  43. package/project/.sdk/src/cmp/js/ReadmeOptions_js.ts +58 -0
  44. package/project/.sdk/src/cmp/js/ReadmeQuick_js.ts +6 -4
  45. package/project/.sdk/src/cmp/js/ReadmeRef_js.ts +384 -0
  46. package/project/.sdk/src/cmp/js/ReadmeTopQuick_js.ts +6 -4
  47. package/project/.sdk/src/cmp/js/TestDirect_js.ts +23 -12
  48. package/project/.sdk/src/cmp/js/TestEntity_js.ts +107 -74
  49. package/project/.sdk/src/cmp/js/fragment/Config.fragment.js +1 -5
  50. package/project/.sdk/src/cmp/lua/Config_lua.ts +9 -4
  51. package/project/.sdk/src/cmp/lua/Package_lua.ts +9 -2
  52. package/project/.sdk/src/cmp/lua/ReadmeEntity_lua.ts +138 -0
  53. package/project/.sdk/src/cmp/lua/ReadmeHowto_lua.ts +6 -3
  54. package/project/.sdk/src/cmp/lua/ReadmeIntro_lua.ts +18 -0
  55. package/project/.sdk/src/cmp/lua/ReadmeModel_lua.ts +6 -3
  56. package/project/.sdk/src/cmp/lua/ReadmeOptions_lua.ts +58 -0
  57. package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +6 -4
  58. package/project/.sdk/src/cmp/lua/ReadmeRef_lua.ts +360 -0
  59. package/project/.sdk/src/cmp/lua/ReadmeTopQuick_lua.ts +6 -4
  60. package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +172 -29
  61. package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +120 -52
  62. package/project/.sdk/src/cmp/lua/fragment/Main.fragment.lua +20 -4
  63. package/project/.sdk/src/cmp/php/Config_php.ts +10 -8
  64. package/project/.sdk/src/cmp/php/Package_php.ts +7 -1
  65. package/project/.sdk/src/cmp/php/ReadmeEntity_php.ts +138 -0
  66. package/project/.sdk/src/cmp/php/ReadmeHowto_php.ts +6 -3
  67. package/project/.sdk/src/cmp/php/ReadmeIntro_php.ts +18 -0
  68. package/project/.sdk/src/cmp/php/ReadmeModel_php.ts +6 -3
  69. package/project/.sdk/src/cmp/php/ReadmeOptions_php.ts +58 -0
  70. package/project/.sdk/src/cmp/php/ReadmeQuick_php.ts +6 -4
  71. package/project/.sdk/src/cmp/php/ReadmeRef_php.ts +358 -0
  72. package/project/.sdk/src/cmp/php/ReadmeTopQuick_php.ts +6 -4
  73. package/project/.sdk/src/cmp/php/TestDirect_php.ts +171 -28
  74. package/project/.sdk/src/cmp/php/TestEntity_php.ts +126 -55
  75. package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +17 -3
  76. package/project/.sdk/src/cmp/py/Config_py.ts +9 -4
  77. package/project/.sdk/src/cmp/py/Package_py.ts +8 -1
  78. package/project/.sdk/src/cmp/py/ReadmeEntity_py.ts +138 -0
  79. package/project/.sdk/src/cmp/py/ReadmeHowto_py.ts +6 -3
  80. package/project/.sdk/src/cmp/py/ReadmeIntro_py.ts +18 -0
  81. package/project/.sdk/src/cmp/py/ReadmeModel_py.ts +6 -3
  82. package/project/.sdk/src/cmp/py/ReadmeOptions_py.ts +58 -0
  83. package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +9 -6
  84. package/project/.sdk/src/cmp/py/ReadmeRef_py.ts +356 -0
  85. package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +9 -6
  86. package/project/.sdk/src/cmp/py/TestDirect_py.ts +164 -27
  87. package/project/.sdk/src/cmp/py/TestEntity_py.ts +125 -51
  88. package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +19 -4
  89. package/project/.sdk/src/cmp/rb/Config_rb.ts +9 -4
  90. package/project/.sdk/src/cmp/rb/Package_rb.ts +9 -2
  91. package/project/.sdk/src/cmp/rb/ReadmeEntity_rb.ts +138 -0
  92. package/project/.sdk/src/cmp/rb/ReadmeHowto_rb.ts +6 -3
  93. package/project/.sdk/src/cmp/rb/ReadmeIntro_rb.ts +18 -0
  94. package/project/.sdk/src/cmp/rb/ReadmeModel_rb.ts +6 -3
  95. package/project/.sdk/src/cmp/rb/ReadmeOptions_rb.ts +58 -0
  96. package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +6 -4
  97. package/project/.sdk/src/cmp/rb/ReadmeRef_rb.ts +361 -0
  98. package/project/.sdk/src/cmp/rb/ReadmeTopQuick_rb.ts +6 -4
  99. package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +172 -29
  100. package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +120 -52
  101. package/project/.sdk/src/cmp/rb/fragment/Main.fragment.rb +19 -3
  102. package/project/.sdk/src/cmp/ts/Config_ts.ts +18 -0
  103. package/project/.sdk/src/cmp/ts/Package_ts.ts +1 -1
  104. package/project/.sdk/src/cmp/ts/ReadmeEntity_ts.ts +138 -0
  105. package/project/.sdk/src/cmp/ts/ReadmeHowto_ts.ts +11 -6
  106. package/project/.sdk/src/cmp/ts/ReadmeIntro_ts.ts +18 -0
  107. package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +9 -5
  108. package/project/.sdk/src/cmp/ts/ReadmeOptions_ts.ts +58 -0
  109. package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +6 -4
  110. package/project/.sdk/src/cmp/ts/ReadmeRef_ts.ts +384 -0
  111. package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +6 -4
  112. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +213 -42
  113. package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +168 -75
  114. package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +1 -5
  115. package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +8 -1
  116. package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +8 -2
  117. package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +21 -1
  118. package/project/.sdk/tm/go/feature/test_feature.go +51 -3
  119. package/project/.sdk/tm/go/test/runner_test.go +106 -6
  120. package/project/.sdk/tm/go/test/sdk-test-control.json +19 -0
  121. package/project/.sdk/tm/go/utility/fetcher.go +10 -0
  122. package/project/.sdk/tm/go/utility/make_url.go +12 -0
  123. package/project/.sdk/tm/go/utility/prepare_auth.go +15 -1
  124. package/project/.sdk/tm/js/src/utility/PrepareAuthUtility.js +7 -1
  125. package/project/.sdk/tm/lua/feature/test_feature.lua +41 -3
  126. package/project/.sdk/tm/lua/test/runner.lua +74 -0
  127. package/project/.sdk/tm/lua/test/sdk-test-control.json +19 -0
  128. package/project/.sdk/tm/lua/utility/fetcher.lua +13 -0
  129. package/project/.sdk/tm/lua/utility/make_url.lua +16 -0
  130. package/project/.sdk/tm/lua/utility/prepare_auth.lua +9 -1
  131. package/project/.sdk/tm/php/feature/TestFeature.php +185 -43
  132. package/project/.sdk/tm/php/test/Runner.php +62 -0
  133. package/project/.sdk/tm/php/test/sdk-test-control.json +19 -0
  134. package/project/.sdk/tm/php/utility/Fetcher.php +132 -9
  135. package/project/.sdk/tm/php/utility/MakeUrl.php +16 -0
  136. package/project/.sdk/tm/php/utility/PrepareAuth.php +11 -1
  137. package/project/.sdk/tm/py/feature/test_feature.py +35 -3
  138. package/project/.sdk/tm/py/test/runner.py +60 -0
  139. package/project/.sdk/tm/py/test/sdk-test-control.json +19 -0
  140. package/project/.sdk/tm/py/utility/fetcher.py +13 -0
  141. package/project/.sdk/tm/py/utility/make_url.py +13 -0
  142. package/project/.sdk/tm/py/utility/prepare_auth.py +10 -1
  143. package/project/.sdk/tm/rb/feature/test_feature.rb +36 -3
  144. package/project/.sdk/tm/rb/test/runner.rb +46 -0
  145. package/project/.sdk/tm/rb/test/sdk-test-control.json +19 -0
  146. package/project/.sdk/tm/rb/utility/fetcher.rb +49 -28
  147. package/project/.sdk/tm/rb/utility/make_url.rb +16 -0
  148. package/project/.sdk/tm/rb/utility/prepare_auth.rb +8 -1
  149. package/project/.sdk/tm/ts/src/utility/MakeUrlUtility.ts +7 -8
  150. package/project/.sdk/tm/ts/src/utility/PrepareAuthUtility.ts +7 -1
  151. package/project/.sdk/tm/ts/test/sdk-test-control.json +19 -0
  152. package/project/.sdk/tm/ts/test/utility.ts +120 -2
  153. package/src/cmp/ReadmeEntity.ts +11 -178
  154. package/src/cmp/ReadmeIntro.ts +11 -25
  155. package/src/cmp/ReadmeModel.ts +7 -5
  156. package/src/cmp/ReadmeOptions.ts +12 -74
  157. package/src/cmp/ReadmeRef.ts +11 -1372
  158. package/src/sdkgen.ts +2 -1
  159. package/src/utility.ts +12 -0
  160. /package/project/.sdk/tm/go/utility/{make_target.go → make_point.go} +0 -0
@@ -1,5 +1,8 @@
1
1
 
2
2
  import {
3
+ Model,
4
+ ModelEntity,
5
+ nom,
3
6
  depluralize,
4
7
  } from '@voxgig/apidef'
5
8
 
@@ -8,6 +11,7 @@ import {
8
11
  File,
9
12
  cmp,
10
13
  snakify,
14
+ isAuthActive,
11
15
  } from '@voxgig/sdkgen'
12
16
 
13
17
 
@@ -51,12 +55,20 @@ function normalizePathParams(
51
55
 
52
56
  const TestDirect = cmp(function TestDirect(props: any) {
53
57
  const ctx$ = props.ctx$
54
- const model = ctx$.model
58
+ const model: Model = ctx$.model
55
59
 
56
60
  const target = props.target
57
- const entity = props.entity
61
+ const entity: ModelEntity = props.entity
58
62
 
59
- const PROJECTNAME = model.Name.toUpperCase().replace(/[^A-Z_]/g, '_')
63
+ const PROJECTNAME = nom(model, 'Name').toUpperCase().replace(/[^A-Z_]/g, '_')
64
+
65
+ const authActive = isAuthActive(model)
66
+ const apikeyEnvEntry = authActive
67
+ ? `\n ["${PROJECTNAME}_APIKEY"] = "NONE",`
68
+ : ''
69
+ const apikeyLiveField = authActive
70
+ ? `\n apikey = env["${PROJECTNAME}_APIKEY"],`
71
+ : ''
60
72
 
61
73
  const opnames = Object.keys(entity.op)
62
74
  const hasLoad = opnames.includes('load')
@@ -71,13 +83,49 @@ const TestDirect = cmp(function TestDirect(props: any) {
71
83
 
72
84
  const loadPoint = loadOp?.points?.[0]
73
85
  const loadPath = loadPoint ? normalizePathParams(loadPoint.parts || [], loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
74
- const loadParams = loadPoint?.args?.params || []
86
+ const allLoadParams = loadPoint?.args?.params || []
87
+ // Some upstream OpenAPI specs declare a parameter as `in: path` even when
88
+ // that path has no `{name}` placeholder for it. Only path params that
89
+ // actually appear in the URL template should drive direct-test path-param
90
+ // setup and URL-substitution asserts; otherwise the SDK silently drops
91
+ // them and the URL-includes assert fails.
92
+ const _pathPlaceholders = new Set<string>()
93
+ for (const part of (loadPoint?.parts || [])) {
94
+ if (typeof part === 'string' && part.startsWith('{') && part.endsWith('}')) {
95
+ _pathPlaceholders.add(part.slice(1, -1))
96
+ }
97
+ }
98
+ const _renameMap = (loadPoint?.rename?.param || {}) as Record<string, string>
99
+ const _renamedPlaceholders = new Set<string>()
100
+ for (const ph of _pathPlaceholders) {
101
+ _renamedPlaceholders.add(ph)
102
+ for (const [orig, renamed] of Object.entries(_renameMap)) {
103
+ if (renamed === ph) _renamedPlaceholders.add(orig)
104
+ }
105
+ }
106
+ const loadParams = allLoadParams.filter((p: any) =>
107
+ _renamedPlaceholders.has(p.name) || _renamedPlaceholders.has(p.orig))
75
108
 
76
109
  const listPoint = listOp?.points?.[0]
77
110
  const listPath = listPoint ? normalizePathParams(listPoint.parts || [], listPoint?.args?.params || [], listPoint?.rename?.param) : ''
78
111
  const listParams = listPoint?.args?.params || []
79
112
 
80
- const entidEnvVar = `${PROJECTNAME}_TEST_${entity.Name.toUpperCase().replace(/[^A-Z_]/g, '_')}_ENTID`
113
+ // Required query params with spec-provided examples — needed in live mode.
114
+ const loadQuery = loadPoint?.args?.query || []
115
+ const loadLiveQueryEntries = loadQuery
116
+ .filter((q: any) => q.reqd && undefined !== q.example && null !== q.example)
117
+ const loadLiveQueryLines = loadLiveQueryEntries
118
+ .map((q: any) => ` query["${q.name}"] = ${JSON.stringify(q.example)}`)
119
+ .join('\n')
120
+
121
+ const loadAllHaveExamples =
122
+ loadParams.length > 0 &&
123
+ loadParams.every((p: any) => undefined !== p.example && null !== p.example)
124
+ const loadExampleLines = loadAllHaveExamples
125
+ ? loadParams.map((p: any) => ` params["${p.name}"] = ${JSON.stringify(p.example)}`).join('\n')
126
+ : ''
127
+
128
+ const entidEnvVar = `${PROJECTNAME}_TEST_${nom(entity, 'NAME').replace(/[^A-Z_]/g, '_')}_ENTID`
81
129
 
82
130
  File({ name: entity.name + '_direct_test.' + target.ext }, () => {
83
131
 
@@ -93,12 +141,33 @@ describe("${entity.Name}Direct", function()
93
141
  `)
94
142
 
95
143
  if (hasList && listPoint) {
144
+ const listLiveIdKeys: string[] = listParams.map((lp: any) => {
145
+ return lp.name === 'id'
146
+ ? entity.name + '01'
147
+ : lp.name.replace(/_id$/, '') + '01'
148
+ })
149
+ const listSkipBlock = listLiveIdKeys.length > 0
150
+ ? ` if setup.live then
151
+ for _, _live_key in ipairs({${listLiveIdKeys.map(k => `"${k}"`).join(', ')}}) do
152
+ if setup.idmap[_live_key] == nil then
153
+ pending("live test needs " .. _live_key .. " via *_ENTID env var (synthetic IDs only)")
154
+ return
155
+ end
156
+ end
157
+ end
158
+ `
159
+ : ''
96
160
  Content(` it("should direct-list-${entity.name}", function()
97
161
  local setup = ${entity.name}_direct_setup({
98
162
  { id = "direct01" },
99
163
  { id = "direct02" },
100
164
  })
101
- local client = setup.client
165
+ local _should_skip, _reason = runner.is_control_skipped("direct", "direct-list-${entity.name}", setup.live and "live" or "unit")
166
+ if _should_skip then
167
+ pending(_reason or "skipped via sdk-test-control.json")
168
+ return
169
+ end
170
+ ${listSkipBlock} local client = setup.client
102
171
 
103
172
  `)
104
173
 
@@ -133,11 +202,27 @@ describe("${entity.Name}Direct", function()
133
202
  `)
134
203
  }
135
204
 
136
- Content(` assert.is_nil(err)
137
- assert.is_true(result["ok"])
138
- assert.are.equal(200, helpers.to_int(result["status"]))
139
-
140
- if not setup.live then
205
+ Content(` if setup.live then
206
+ -- Live mode is lenient: synthetic IDs frequently 4xx and the list-
207
+ -- response shape varies wildly across public APIs. Skip rather than
208
+ -- fail when the call doesn't return a usable list.
209
+ if err ~= nil then
210
+ pending("list call failed (likely synthetic IDs against live API): " .. tostring(err))
211
+ return
212
+ end
213
+ if not result["ok"] then
214
+ pending("list call not ok (likely synthetic IDs against live API)")
215
+ return
216
+ end
217
+ local status = helpers.to_int(result["status"])
218
+ if status < 200 or status >= 300 then
219
+ pending("expected 2xx status, got " .. tostring(status))
220
+ return
221
+ end
222
+ else
223
+ assert.is_nil(err)
224
+ assert.is_true(result["ok"])
225
+ assert.are.equal(200, helpers.to_int(result["status"]))
141
226
  assert.is_table(result["data"])
142
227
  assert.are.equal(2, #result["data"])
143
228
  assert.are.equal(1, #setup.calls)
@@ -148,22 +233,65 @@ describe("${entity.Name}Direct", function()
148
233
  }
149
234
 
150
235
  if (hasLoad && loadPoint) {
236
+ // Skip live direct-load only when we can't fill path params:
237
+ // no spec examples and no list-bootstrap. Spec examples win first.
238
+ const loadSkipBlock = (loadParams.length > 0 && !loadAllHaveExamples)
239
+ ? ` if setup.live then
240
+ pending("live direct-load needs real ID — set *_ENTID env var with real IDs to run")
241
+ return
242
+ end
243
+ `
244
+ : ''
151
245
  Content(` it("should direct-load-${entity.name}", function()
152
246
  local setup = ${entity.name}_direct_setup({ id = "direct01" })
153
- local client = setup.client
247
+ local _should_skip, _reason = runner.is_control_skipped("direct", "direct-load-${entity.name}", setup.live and "live" or "unit")
248
+ if _should_skip then
249
+ pending(_reason or "skipped via sdk-test-control.json")
250
+ return
251
+ end
252
+ ${loadSkipBlock} local client = setup.client
154
253
 
155
254
  `)
156
255
 
157
- if (loadParams.length > 0) {
256
+ const needsQuery = loadParams.length > 0 || loadLiveQueryLines !== ''
257
+ if (needsQuery) {
158
258
  Content(` local params = {}
159
- if not setup.live then
259
+ local query = {}
160
260
  `)
161
- for (let i = 0; i < loadParams.length; i++) {
162
- Content(` params["${loadParams[i].name}"] = "direct0${i + 1}"
261
+ if (loadAllHaveExamples) {
262
+ Content(` if setup.live then
163
263
  `)
164
- }
165
- Content(` end
264
+ if (loadLiveQueryLines) Content(loadLiveQueryLines + '\n')
265
+ Content(loadExampleLines + '\n')
266
+ Content(` else
166
267
  `)
268
+ for (let i = 0; i < loadParams.length; i++) {
269
+ Content(` params["${loadParams[i].name}"] = "direct0${i + 1}"
270
+ `)
271
+ }
272
+ Content(` end
273
+ `)
274
+ } else if (loadParams.length > 0) {
275
+ if (loadLiveQueryLines) {
276
+ Content(` if setup.live then
277
+ ${loadLiveQueryLines}
278
+ end
279
+ `)
280
+ }
281
+ Content(` if not setup.live then
282
+ `)
283
+ for (let i = 0; i < loadParams.length; i++) {
284
+ Content(` params["${loadParams[i].name}"] = "direct0${i + 1}"
285
+ `)
286
+ }
287
+ Content(` end
288
+ `)
289
+ } else if (loadLiveQueryLines) {
290
+ Content(` if setup.live then
291
+ ${loadLiveQueryLines}
292
+ end
293
+ `)
294
+ }
167
295
  }
168
296
 
169
297
  Content(`
@@ -171,20 +299,37 @@ describe("${entity.Name}Direct", function()
171
299
  path = "${loadPath}",
172
300
  method = "GET",
173
301
  `)
174
- if (loadParams.length > 0) {
302
+ if (needsQuery) {
175
303
  Content(` params = params,
304
+ query = query,
176
305
  `)
177
306
  } else {
178
307
  Content(` params = {},
179
308
  `)
180
309
  }
181
310
  Content(` })
182
- assert.is_nil(err)
183
- assert.is_true(result["ok"])
184
- assert.are.equal(200, helpers.to_int(result["status"]))
185
- assert.is_not_nil(result["data"])
186
-
187
- if not setup.live then
311
+ if setup.live then
312
+ -- Live mode is lenient: synthetic IDs frequently 4xx. Skip rather
313
+ -- than fail when the load endpoint isn't reachable with the IDs we
314
+ -- can construct from setup.idmap.
315
+ if err ~= nil then
316
+ pending("load call failed (likely synthetic IDs against live API): " .. tostring(err))
317
+ return
318
+ end
319
+ if not result["ok"] then
320
+ pending("load call not ok (likely synthetic IDs against live API)")
321
+ return
322
+ end
323
+ local status = helpers.to_int(result["status"])
324
+ if status < 200 or status >= 300 then
325
+ pending("expected 2xx status, got " .. tostring(status))
326
+ return
327
+ end
328
+ else
329
+ assert.is_nil(err)
330
+ assert.is_true(result["ok"])
331
+ assert.are.equal(200, helpers.to_int(result["status"]))
332
+ assert.is_not_nil(result["data"])
188
333
  if type(result["data"]) == "table" then
189
334
  assert.are.equal("direct01", result["data"]["id"])
190
335
  end
@@ -205,15 +350,13 @@ function ${entity.name}_direct_setup(mockres)
205
350
 
206
351
  local env = runner.env_override({
207
352
  ["${entidEnvVar}"] = {},
208
- ["${PROJECTNAME}_TEST_LIVE"] = "FALSE",
209
- ["${PROJECTNAME}_APIKEY"] = "NONE",
353
+ ["${PROJECTNAME}_TEST_LIVE"] = "FALSE",${apikeyEnvEntry}
210
354
  })
211
355
 
212
356
  local live = env["${PROJECTNAME}_TEST_LIVE"] == "TRUE"
213
357
 
214
358
  if live then
215
- local merged_opts = {
216
- apikey = env["${PROJECTNAME}_APIKEY"],
359
+ local merged_opts = {${apikeyLiveField}
217
360
  }
218
361
  local client = sdk.new(merged_opts)
219
362
  return {
@@ -7,7 +7,12 @@ import {
7
7
 
8
8
  import {
9
9
  KIT,
10
+ Model,
11
+ ModelEntity,
12
+ ModelEntityFlow,
13
+ ModelEntityFlowStep,
10
14
  getModelPath,
15
+ nom,
11
16
  } from '@voxgig/apidef'
12
17
 
13
18
 
@@ -18,34 +23,43 @@ import {
18
23
  each,
19
24
  buildIdNames,
20
25
  getMatchEntries,
26
+ isAuthActive,
21
27
  } from '@voxgig/sdkgen'
22
28
 
23
29
 
24
- type OpGen = (ctx: GenCtx, step: any, index: any) => void
25
-
30
+ // See TestEntity_ts.ts for the GenCtx/OpGen contract.
26
31
  type GenCtx = {
27
- model: any
28
- entity: any
29
- flow: any
32
+ model: Model
33
+ entity: ModelEntity
34
+ flow: ModelEntityFlow
30
35
  PROJUPPER: string
31
36
  }
32
37
 
38
+ type OpGen = (ctx: GenCtx, step: ModelEntityFlowStep, index: number) => void
39
+
33
40
 
34
41
  const TestEntity = cmp(function TestEntity(props: any) {
35
42
  const ctx$ = props.ctx$
36
- const model = ctx$.model
43
+ const model: Model = ctx$.model
37
44
 
38
45
  const target = props.target
39
- const entity = props.entity
46
+ const entity: ModelEntity = props.entity
40
47
 
41
- const basicflow = getModelPath(model, `main.${KIT}.flow.Basic${entity.Name}Flow`)
42
- const dobasic = basicflow && true === basicflow.active
43
-
44
- if (!dobasic) {
48
+ const basicflow: ModelEntityFlow | undefined =
49
+ getModelPath(model, `main.${KIT}.flow.Basic${nom(entity, 'Name')}Flow`)
50
+ if (null == basicflow || true !== basicflow.active) {
45
51
  return
46
52
  }
47
53
 
48
- const PROJUPPER = model.const.Name.toUpperCase().replace(/[^A-Z_]/g, '_')
54
+ const PROJUPPER = nom(model.const, 'Name').toUpperCase().replace(/[^A-Z_]/g, '_')
55
+
56
+ const authActive = isAuthActive(model)
57
+ const apikeyEnvEntry = authActive
58
+ ? `\n ["${PROJUPPER}_APIKEY"] = "NONE",`
59
+ : ''
60
+ const apikeyLiveField = authActive
61
+ ? `\n apikey = env["${PROJUPPER}_APIKEY"],`
62
+ : ''
49
63
 
50
64
  const idnames = buildIdNames(entity, basicflow)
51
65
  const idnamesStr = idnames.map(n => `"${n}"`).join(', ')
@@ -80,6 +94,21 @@ describe("${entity.Name}Entity", function()
80
94
 
81
95
  it("should run basic flow", function()
82
96
  local setup = ${entity.name}_basic_setup(nil)
97
+ -- Per-op sdk-test-control.json skip.
98
+ local _live = setup.live or false
99
+ for _, _op in ipairs({${(Array.from(new Set((allSteps as any[]).map((s: any) => s.op).filter(Boolean)))).map(o => `"${o}"`).join(', ')}}) do
100
+ local _should_skip, _reason = runner.is_control_skipped("entityOp", "${entity.name}." .. _op, _live and "live" or "unit")
101
+ if _should_skip then
102
+ pending(_reason or "skipped via sdk-test-control.json")
103
+ return
104
+ end
105
+ end
106
+ -- The basic flow consumes synthetic IDs from the fixture. In live mode
107
+ -- without an *_ENTID env override, those IDs hit the live API and 4xx.
108
+ if setup.synthetic_only then
109
+ pending("live entity test uses synthetic IDs from fixture — set ${PROJUPPER}_TEST_${entity.name.toUpperCase().replace(/[^A-Z_]/g, '_')}_ENTID JSON to run live")
110
+ return
111
+ end
83
112
  local client = setup.client
84
113
 
85
114
  `)
@@ -147,11 +176,16 @@ end)
147
176
 
148
177
  `)
149
178
 
150
- Content(` local env = runner.env_override({
179
+ Content(` -- Detect ENTID env override before envOverride consumes it. When live
180
+ -- mode is on without a real override, the basic test runs against synthetic
181
+ -- IDs from the fixture and 4xx's. Surface this so the test can skip.
182
+ local entid_env_raw = os.getenv("${PROJUPPER}_TEST_${entity.name.toUpperCase().replace(/[^A-Z_]/g, '_')}_ENTID")
183
+ local idmap_overridden = entid_env_raw ~= nil and entid_env_raw:match("^%s*{") ~= nil
184
+
185
+ local env = runner.env_override({
151
186
  ["${PROJUPPER}_TEST_${entity.name.toUpperCase().replace(/[^A-Z_]/g, '_')}_ENTID"] = idmap,
152
187
  ["${PROJUPPER}_TEST_LIVE"] = "FALSE",
153
- ["${PROJUPPER}_TEST_EXPLAIN"] = "FALSE",
154
- ["${PROJUPPER}_APIKEY"] = "NONE",
188
+ ["${PROJUPPER}_TEST_EXPLAIN"] = "FALSE",${apikeyEnvEntry}
155
189
  })
156
190
 
157
191
  local idmap_resolved = helpers.to_map(
@@ -172,20 +206,22 @@ end)
172
206
  Content(`
173
207
  if env["${PROJUPPER}_TEST_LIVE"] == "TRUE" then
174
208
  local merged_opts = vs.merge({
175
- {
176
- apikey = env["${PROJUPPER}_APIKEY"],
209
+ {${apikeyLiveField}
177
210
  },
178
211
  extra or {},
179
212
  })
180
213
  client = sdk.new(helpers.to_map(merged_opts))
181
214
  end
182
215
 
216
+ local live = env["${PROJUPPER}_TEST_LIVE"] == "TRUE"
183
217
  return {
184
218
  client = client,
185
219
  data = entity_data,
186
220
  idmap = idmap_resolved,
187
221
  env = env,
188
222
  explain = env["${PROJUPPER}_TEST_EXPLAIN"] == "TRUE",
223
+ live = live,
224
+ synthetic_only = live and not idmap_overridden,
189
225
  now = os.time() * 1000,
190
226
  }
191
227
  end
@@ -196,9 +232,9 @@ end
196
232
 
197
233
  const generateCreate: OpGen = (ctx, step, index) => {
198
234
  const { entity, flow } = ctx
199
- const ref = step.input?.ref ?? entity.name + '_ref01'
200
- const entvar = step.input?.entvar ?? ref + '_ent'
201
- const datavar = step.input?.datavar ?? (ref + '_data' + (step.input?.suffix ?? ''))
235
+ const ref = step.input.ref ?? entity.name + '_ref01'
236
+ const entvar = step.input.entvar ?? ref + '_ent'
237
+ const datavar = step.input.datavar ?? (ref + '_data' + (step.input.suffix ?? ''))
202
238
 
203
239
  const priorSteps = Object.values(flow.step).slice(0, Number(index)) as any[]
204
240
  const needsEnt = !priorSteps.some((s: any) =>
@@ -206,8 +242,8 @@ const generateCreate: OpGen = (ctx, step, index) => {
206
242
 
207
243
  const hasDatvar = priorSteps.some((s: any) => {
208
244
  if ('create' === s.op) {
209
- const priorRef = s.input?.ref ?? entity.name + '_ref01'
210
- const priorDatvar = s.input?.datavar ?? (priorRef + '_data' + (s.input?.suffix ?? ''))
245
+ const priorRef = s.input.ref ?? entity.name + '_ref01'
246
+ const priorDatvar = s.input.datavar ?? (priorRef + '_data' + (s.input.suffix ?? ''))
211
247
  return priorDatvar === datavar
212
248
  }
213
249
  return false
@@ -241,17 +277,20 @@ const generateCreate: OpGen = (ctx, step, index) => {
241
277
  assert.is_nil(err)
242
278
  ${datavar} = helpers.to_map(${datavar}_result)
243
279
  assert.is_not_nil(${datavar})
244
- assert.is_not_nil(${datavar}["id"])
245
280
  `)
281
+ if (null != ctx.entity.id) {
282
+ Content(` assert.is_not_nil(${datavar}["id"])
283
+ `)
284
+ }
246
285
  }
247
286
 
248
287
 
249
288
  const generateList: OpGen = (ctx, step, index) => {
250
289
  const { entity, flow } = ctx
251
- const ref = step.input?.ref ?? entity.name + '_ref01'
252
- const entvar = step.input?.entvar ?? ref + '_ent'
253
- const matchvar = step.input?.matchvar ?? (ref + '_match' + (step.input?.suffix ?? ''))
254
- const listvar = step.input?.listvar ?? (ref + '_list' + (step.input?.suffix ?? ''))
290
+ const ref = step.input.ref ?? entity.name + '_ref01'
291
+ const entvar = step.input.entvar ?? ref + '_ent'
292
+ const matchvar = step.input.matchvar ?? (ref + '_match' + (step.input.suffix ?? ''))
293
+ const listvar = step.input.listvar ?? (ref + '_list' + (step.input.suffix ?? ''))
255
294
 
256
295
  const priorSteps = Object.values(flow.step).slice(0, Number(index)) as any[]
257
296
  const needsEnt = !priorSteps.some((s: any) =>
@@ -291,7 +330,7 @@ const generateList: OpGen = (ctx, step, index) => {
291
330
  for (const validator of step.valid) {
292
331
  const validRef = validator.def?.ref
293
332
  const hasRefData = validRef && allSteps.some((s: any) => 'create' === s.op &&
294
- ((s.input?.ref ?? entity.name + '_ref01') === validRef))
333
+ ((s.input.ref ?? entity.name + '_ref01') === validRef))
295
334
 
296
335
  if ('ItemExists' === validator.apply && hasRefData) {
297
336
  const refDataVar = validRef + '_data'
@@ -317,17 +356,19 @@ const generateList: OpGen = (ctx, step, index) => {
317
356
 
318
357
  const generateUpdate: OpGen = (ctx, step, index) => {
319
358
  const { entity, flow } = ctx
320
- const ref = step.input?.ref ?? entity.name + '_ref01'
321
- const entvar = step.input?.entvar ?? ref + '_ent'
322
- const datavar = step.input?.datavar ?? (ref + '_data' + (step.input?.suffix ?? ''))
323
- const resdatavar = step.input?.resdatavar ?? (ref + '_resdata' + (step.input?.suffix ?? ''))
324
- const markdefvar = step.input?.markdefvar ?? (ref + '_markdef' + (step.input?.suffix ?? ''))
325
- const srcdatavar = step.input?.srcdatavar ?? (ref + '_data' + (step.input?.suffix ?? ''))
359
+ const ref = step.input.ref ?? entity.name + '_ref01'
360
+ const entvar = step.input.entvar ?? ref + '_ent'
361
+ const datavar = step.input.datavar ?? (ref + '_data' + (step.input.suffix ?? ''))
362
+ const resdatavar = step.input.resdatavar ?? (ref + '_resdata' + (step.input.suffix ?? ''))
363
+ const markdefvar = step.input.markdefvar ?? (ref + '_markdef' + (step.input.suffix ?? ''))
364
+ const srcdatavar = step.input.srcdatavar ?? (ref + '_data' + (step.input.suffix ?? ''))
326
365
 
327
366
  const priorSteps = Object.values(flow.step).slice(0, Number(index)) as any[]
328
367
  const needsEnt = !priorSteps.some((s: any) =>
329
368
  ['create', 'list', 'load', 'update', 'remove'].includes(s.op))
330
369
 
370
+ const hasEntIdU = null != entity.id
371
+
331
372
  Content(` -- UPDATE
332
373
  `)
333
374
  if (needsEnt) {
@@ -335,8 +376,11 @@ const generateUpdate: OpGen = (ctx, step, index) => {
335
376
  `)
336
377
  }
337
378
  Content(` local ${datavar}_up = {
338
- id = ${srcdatavar}["id"],
339
379
  `)
380
+ if (hasEntIdU) {
381
+ Content(` id = ${srcdatavar}["id"],
382
+ `)
383
+ }
340
384
 
341
385
  if (step.data) {
342
386
  const dataEntries = Object.entries(step.data).filter(([k]: any) => k !== 'id' && !k.endsWith('$'))
@@ -351,7 +395,7 @@ const generateUpdate: OpGen = (ctx, step, index) => {
351
395
 
352
396
  if (step.spec) {
353
397
  for (const spec of step.spec) {
354
- if ('TextFieldMark' === spec.apply && null != step.input?.textfield) {
398
+ if ('TextFieldMark' === spec.apply && null != step.input.textfield) {
355
399
  const fieldname = step.input.textfield
356
400
  const fieldvalue = spec.def?.mark ?? `Mark01-${ref}`
357
401
  Content(`
@@ -368,12 +412,15 @@ const generateUpdate: OpGen = (ctx, step, index) => {
368
412
  assert.is_nil(err)
369
413
  local ${resdatavar} = helpers.to_map(${resdatavar}_result)
370
414
  assert.is_not_nil(${resdatavar})
371
- assert.are.equal(${resdatavar}["id"], ${datavar}_up["id"])
372
415
  `)
416
+ if (hasEntIdU) {
417
+ Content(` assert.are.equal(${resdatavar}["id"], ${datavar}_up["id"])
418
+ `)
419
+ }
373
420
 
374
421
  if (step.spec) {
375
422
  for (const spec of step.spec) {
376
- if ('TextFieldMark' === spec.apply && null != step.input?.textfield) {
423
+ if ('TextFieldMark' === spec.apply && null != step.input.textfield) {
377
424
  Content(` assert.are.equal(${resdatavar}[${markdefvar}_name], ${markdefvar}_value)
378
425
  `)
379
426
  }
@@ -384,11 +431,11 @@ const generateUpdate: OpGen = (ctx, step, index) => {
384
431
 
385
432
  const generateLoad: OpGen = (ctx, step, index) => {
386
433
  const { entity, flow } = ctx
387
- const ref = step.input?.ref ?? entity.name + '_ref01'
388
- const entvar = step.input?.entvar ?? ref + '_ent'
389
- const matchvar = step.input?.matchvar ?? (ref + '_match' + (step.input?.suffix ?? ''))
390
- const datavar = step.input?.datavar ?? (ref + '_data' + (step.input?.suffix ?? ''))
391
- const srcdatavar = step.input?.srcdatavar ?? (ref + '_data' + (step.input?.suffix ?? ''))
434
+ const ref = step.input.ref ?? entity.name + '_ref01'
435
+ const entvar = step.input.entvar ?? ref + '_ent'
436
+ const matchvar = step.input.matchvar ?? (ref + '_match' + (step.input.suffix ?? ''))
437
+ const datavar = step.input.datavar ?? (ref + '_data' + (step.input.suffix ?? ''))
438
+ const srcdatavar = step.input.srcdatavar ?? (ref + '_data' + (step.input.suffix ?? ''))
392
439
 
393
440
  const priorSteps = Object.values(flow.step).slice(0, Number(index)) as any[]
394
441
  const hasEntVar = priorSteps.some((s: any) =>
@@ -399,20 +446,22 @@ const generateLoad: OpGen = (ctx, step, index) => {
399
446
  const hasSrcData = (!flowHasCreate && srcdatavar === (preambleRef + '_data')) ||
400
447
  priorSteps.some((s: any) => {
401
448
  if ('create' === s.op) {
402
- const priorRef = s.input?.ref ?? entity.name + '_ref01'
403
- const priorDatvar = s.input?.datavar ?? (priorRef + '_data' + (s.input?.suffix ?? ''))
449
+ const priorRef = s.input.ref ?? entity.name + '_ref01'
450
+ const priorDatvar = s.input.datavar ?? (priorRef + '_data' + (s.input.suffix ?? ''))
404
451
  return priorDatvar === srcdatavar
405
452
  }
406
453
  return false
407
454
  })
408
455
 
456
+ const hasEntId = null != entity.id
457
+
409
458
  Content(` -- LOAD
410
459
  `)
411
460
  if (!hasEntVar) {
412
461
  Content(` local ${entvar} = client:${entity.Name}(nil)
413
462
  `)
414
463
  }
415
- if (!hasSrcData) {
464
+ if (!hasSrcData && hasEntId) {
416
465
  Content(` local ${srcdatavar}_raw = vs.items(helpers.to_map(
417
466
  vs.getpath(setup.data, "existing.${entity.name}")))
418
467
  local ${srcdatavar} = nil
@@ -421,7 +470,8 @@ const generateLoad: OpGen = (ctx, step, index) => {
421
470
  end
422
471
  `)
423
472
  }
424
- Content(` local ${matchvar} = {
473
+ if (hasEntId) {
474
+ Content(` local ${matchvar} = {
425
475
  id = ${srcdatavar}["id"],
426
476
  }
427
477
  local ${datavar}_loaded, err = ${entvar}:load(${matchvar}, nil)
@@ -430,32 +480,50 @@ const generateLoad: OpGen = (ctx, step, index) => {
430
480
  assert.is_not_nil(${datavar}_load_result)
431
481
  assert.are.equal(${datavar}_load_result["id"], ${srcdatavar}["id"])
432
482
  `)
483
+ }
484
+ else {
485
+ Content(` local ${matchvar} = {}
486
+ local ${datavar}_loaded, err = ${entvar}:load(${matchvar}, nil)
487
+ assert.is_nil(err)
488
+ assert.is_not_nil(${datavar}_loaded)
489
+ `)
490
+ }
433
491
  }
434
492
 
435
493
 
436
494
  const generateRemove: OpGen = (ctx, step, index) => {
437
495
  const { entity, flow } = ctx
438
- const ref = step.input?.ref ?? entity.name + '_ref01'
439
- const entvar = step.input?.entvar ?? ref + '_ent'
440
- const matchvar = step.input?.matchvar ?? (ref + '_match' + (step.input?.suffix ?? ''))
441
- const srcdatavar = step.input?.srcdatavar ?? (ref + '_data')
496
+ const ref = step.input.ref ?? entity.name + '_ref01'
497
+ const entvar = step.input.entvar ?? ref + '_ent'
498
+ const matchvar = step.input.matchvar ?? (ref + '_match' + (step.input.suffix ?? ''))
499
+ const srcdatavar = step.input.srcdatavar ?? (ref + '_data')
442
500
 
443
501
  const priorSteps = Object.values(flow.step).slice(0, Number(index)) as any[]
444
502
  const needsEnt = !priorSteps.some((s: any) =>
445
503
  ['create', 'list', 'load', 'update', 'remove'].includes(s.op))
446
504
 
505
+ const hasEntIdR = null != entity.id
506
+
447
507
  Content(` -- REMOVE
448
508
  `)
449
509
  if (needsEnt) {
450
510
  Content(` local ${entvar} = client:${entity.Name}(nil)
451
511
  `)
452
512
  }
453
- Content(` local ${matchvar} = {
513
+ if (hasEntIdR) {
514
+ Content(` local ${matchvar} = {
454
515
  id = ${srcdatavar}["id"],
455
516
  }
456
517
  local _, err = ${entvar}:remove(${matchvar}, nil)
457
518
  assert.is_nil(err)
458
519
  `)
520
+ }
521
+ else {
522
+ Content(` local ${matchvar} = {}
523
+ local _, err = ${entvar}:remove(${matchvar}, nil)
524
+ assert.is_nil(err)
525
+ `)
526
+ }
459
527
  }
460
528
 
461
529