@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.
- package/bin/voxgig-sdkgen +1 -1
- package/dist/cmp/ReadmeEntity.js +9 -153
- package/dist/cmp/ReadmeEntity.js.map +1 -1
- package/dist/cmp/ReadmeIntro.js +9 -14
- package/dist/cmp/ReadmeIntro.js.map +1 -1
- package/dist/cmp/ReadmeModel.js +6 -4
- package/dist/cmp/ReadmeModel.js.map +1 -1
- package/dist/cmp/ReadmeOptions.js +9 -61
- package/dist/cmp/ReadmeOptions.js.map +1 -1
- package/dist/cmp/ReadmeRef.js +10 -1328
- package/dist/cmp/ReadmeRef.js.map +1 -1
- package/dist/sdkgen.d.ts +2 -2
- package/dist/sdkgen.js +2 -1
- package/dist/sdkgen.js.map +1 -1
- package/dist/utility.d.ts +2 -1
- package/dist/utility.js +9 -0
- package/dist/utility.js.map +1 -1
- package/package.json +3 -3
- package/project/.sdk/src/cmp/go/Config_go.ts +9 -4
- package/project/.sdk/src/cmp/go/Entity_go.ts +2 -2
- package/project/.sdk/src/cmp/go/Main_go.ts +8 -4
- package/project/.sdk/src/cmp/go/Package_go.ts +2 -2
- package/project/.sdk/src/cmp/go/ReadmeEntity_go.ts +138 -0
- package/project/.sdk/src/cmp/go/ReadmeExplanation_go.ts +2 -2
- package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +8 -5
- package/project/.sdk/src/cmp/go/ReadmeInstall_go.ts +2 -2
- package/project/.sdk/src/cmp/go/ReadmeIntro_go.ts +18 -0
- package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +8 -5
- package/project/.sdk/src/cmp/go/ReadmeOptions_go.ts +58 -0
- package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +13 -9
- package/project/.sdk/src/cmp/go/ReadmeRef_go.ts +354 -0
- package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +8 -6
- package/project/.sdk/src/cmp/go/ReadmeTopTest_go.ts +2 -2
- package/project/.sdk/src/cmp/go/TestDirect_go.ts +222 -41
- package/project/.sdk/src/cmp/go/TestEntity_go.ts +142 -60
- package/project/.sdk/src/cmp/go/Test_go.ts +2 -2
- package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +21 -4
- package/project/.sdk/src/cmp/js/Config_js.ts +18 -0
- package/project/.sdk/src/cmp/js/ReadmeEntity_js.ts +138 -0
- package/project/.sdk/src/cmp/js/ReadmeHowto_js.ts +11 -6
- package/project/.sdk/src/cmp/js/ReadmeIntro_js.ts +18 -0
- package/project/.sdk/src/cmp/js/ReadmeModel_js.ts +6 -3
- package/project/.sdk/src/cmp/js/ReadmeOptions_js.ts +58 -0
- package/project/.sdk/src/cmp/js/ReadmeQuick_js.ts +6 -4
- package/project/.sdk/src/cmp/js/ReadmeRef_js.ts +384 -0
- package/project/.sdk/src/cmp/js/ReadmeTopQuick_js.ts +6 -4
- package/project/.sdk/src/cmp/js/TestDirect_js.ts +23 -12
- package/project/.sdk/src/cmp/js/TestEntity_js.ts +107 -74
- package/project/.sdk/src/cmp/js/fragment/Config.fragment.js +1 -5
- package/project/.sdk/src/cmp/lua/Config_lua.ts +9 -4
- package/project/.sdk/src/cmp/lua/Package_lua.ts +9 -2
- package/project/.sdk/src/cmp/lua/ReadmeEntity_lua.ts +138 -0
- package/project/.sdk/src/cmp/lua/ReadmeHowto_lua.ts +6 -3
- package/project/.sdk/src/cmp/lua/ReadmeIntro_lua.ts +18 -0
- package/project/.sdk/src/cmp/lua/ReadmeModel_lua.ts +6 -3
- package/project/.sdk/src/cmp/lua/ReadmeOptions_lua.ts +58 -0
- package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +6 -4
- package/project/.sdk/src/cmp/lua/ReadmeRef_lua.ts +360 -0
- package/project/.sdk/src/cmp/lua/ReadmeTopQuick_lua.ts +6 -4
- package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +172 -29
- package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +120 -52
- package/project/.sdk/src/cmp/lua/fragment/Main.fragment.lua +20 -4
- package/project/.sdk/src/cmp/php/Config_php.ts +10 -8
- package/project/.sdk/src/cmp/php/Package_php.ts +7 -1
- package/project/.sdk/src/cmp/php/ReadmeEntity_php.ts +138 -0
- package/project/.sdk/src/cmp/php/ReadmeHowto_php.ts +6 -3
- package/project/.sdk/src/cmp/php/ReadmeIntro_php.ts +18 -0
- package/project/.sdk/src/cmp/php/ReadmeModel_php.ts +6 -3
- package/project/.sdk/src/cmp/php/ReadmeOptions_php.ts +58 -0
- package/project/.sdk/src/cmp/php/ReadmeQuick_php.ts +6 -4
- package/project/.sdk/src/cmp/php/ReadmeRef_php.ts +358 -0
- package/project/.sdk/src/cmp/php/ReadmeTopQuick_php.ts +6 -4
- package/project/.sdk/src/cmp/php/TestDirect_php.ts +171 -28
- package/project/.sdk/src/cmp/php/TestEntity_php.ts +126 -55
- package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +17 -3
- package/project/.sdk/src/cmp/py/Config_py.ts +9 -4
- package/project/.sdk/src/cmp/py/Package_py.ts +8 -1
- package/project/.sdk/src/cmp/py/ReadmeEntity_py.ts +138 -0
- package/project/.sdk/src/cmp/py/ReadmeHowto_py.ts +6 -3
- package/project/.sdk/src/cmp/py/ReadmeIntro_py.ts +18 -0
- package/project/.sdk/src/cmp/py/ReadmeModel_py.ts +6 -3
- package/project/.sdk/src/cmp/py/ReadmeOptions_py.ts +58 -0
- package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +9 -6
- package/project/.sdk/src/cmp/py/ReadmeRef_py.ts +356 -0
- package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +9 -6
- package/project/.sdk/src/cmp/py/TestDirect_py.ts +164 -27
- package/project/.sdk/src/cmp/py/TestEntity_py.ts +125 -51
- package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +19 -4
- package/project/.sdk/src/cmp/rb/Config_rb.ts +9 -4
- package/project/.sdk/src/cmp/rb/Package_rb.ts +9 -2
- package/project/.sdk/src/cmp/rb/ReadmeEntity_rb.ts +138 -0
- package/project/.sdk/src/cmp/rb/ReadmeHowto_rb.ts +6 -3
- package/project/.sdk/src/cmp/rb/ReadmeIntro_rb.ts +18 -0
- package/project/.sdk/src/cmp/rb/ReadmeModel_rb.ts +6 -3
- package/project/.sdk/src/cmp/rb/ReadmeOptions_rb.ts +58 -0
- package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +6 -4
- package/project/.sdk/src/cmp/rb/ReadmeRef_rb.ts +361 -0
- package/project/.sdk/src/cmp/rb/ReadmeTopQuick_rb.ts +6 -4
- package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +172 -29
- package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +120 -52
- package/project/.sdk/src/cmp/rb/fragment/Main.fragment.rb +19 -3
- package/project/.sdk/src/cmp/ts/Config_ts.ts +18 -0
- package/project/.sdk/src/cmp/ts/Package_ts.ts +1 -1
- package/project/.sdk/src/cmp/ts/ReadmeEntity_ts.ts +138 -0
- package/project/.sdk/src/cmp/ts/ReadmeHowto_ts.ts +11 -6
- package/project/.sdk/src/cmp/ts/ReadmeIntro_ts.ts +18 -0
- package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +9 -5
- package/project/.sdk/src/cmp/ts/ReadmeOptions_ts.ts +58 -0
- package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +6 -4
- package/project/.sdk/src/cmp/ts/ReadmeRef_ts.ts +384 -0
- package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +6 -4
- package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +213 -42
- package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +168 -75
- package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +1 -5
- package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +8 -1
- package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +8 -2
- package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +21 -1
- package/project/.sdk/tm/go/feature/test_feature.go +51 -3
- package/project/.sdk/tm/go/test/runner_test.go +106 -6
- package/project/.sdk/tm/go/test/sdk-test-control.json +19 -0
- package/project/.sdk/tm/go/utility/fetcher.go +10 -0
- package/project/.sdk/tm/go/utility/make_url.go +12 -0
- package/project/.sdk/tm/go/utility/prepare_auth.go +15 -1
- package/project/.sdk/tm/js/src/utility/PrepareAuthUtility.js +7 -1
- package/project/.sdk/tm/lua/feature/test_feature.lua +41 -3
- package/project/.sdk/tm/lua/test/runner.lua +74 -0
- package/project/.sdk/tm/lua/test/sdk-test-control.json +19 -0
- package/project/.sdk/tm/lua/utility/fetcher.lua +13 -0
- package/project/.sdk/tm/lua/utility/make_url.lua +16 -0
- package/project/.sdk/tm/lua/utility/prepare_auth.lua +9 -1
- package/project/.sdk/tm/php/feature/TestFeature.php +185 -43
- package/project/.sdk/tm/php/test/Runner.php +62 -0
- package/project/.sdk/tm/php/test/sdk-test-control.json +19 -0
- package/project/.sdk/tm/php/utility/Fetcher.php +132 -9
- package/project/.sdk/tm/php/utility/MakeUrl.php +16 -0
- package/project/.sdk/tm/php/utility/PrepareAuth.php +11 -1
- package/project/.sdk/tm/py/feature/test_feature.py +35 -3
- package/project/.sdk/tm/py/test/runner.py +60 -0
- package/project/.sdk/tm/py/test/sdk-test-control.json +19 -0
- package/project/.sdk/tm/py/utility/fetcher.py +13 -0
- package/project/.sdk/tm/py/utility/make_url.py +13 -0
- package/project/.sdk/tm/py/utility/prepare_auth.py +10 -1
- package/project/.sdk/tm/rb/feature/test_feature.rb +36 -3
- package/project/.sdk/tm/rb/test/runner.rb +46 -0
- package/project/.sdk/tm/rb/test/sdk-test-control.json +19 -0
- package/project/.sdk/tm/rb/utility/fetcher.rb +49 -28
- package/project/.sdk/tm/rb/utility/make_url.rb +16 -0
- package/project/.sdk/tm/rb/utility/prepare_auth.rb +8 -1
- package/project/.sdk/tm/ts/src/utility/MakeUrlUtility.ts +7 -8
- package/project/.sdk/tm/ts/src/utility/PrepareAuthUtility.ts +7 -1
- package/project/.sdk/tm/ts/test/sdk-test-control.json +19 -0
- package/project/.sdk/tm/ts/test/utility.ts +120 -2
- package/src/cmp/ReadmeEntity.ts +11 -178
- package/src/cmp/ReadmeIntro.ts +11 -25
- package/src/cmp/ReadmeModel.ts +7 -5
- package/src/cmp/ReadmeOptions.ts +12 -74
- package/src/cmp/ReadmeRef.ts +11 -1372
- package/src/sdkgen.ts +2 -1
- package/src/utility.ts +12 -0
- /package/project/.sdk/tm/go/utility/{make_target.go → make_point.go} +0 -0
|
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|
|
4
4
|
// ProjectName SDK test feature
|
|
5
5
|
|
|
6
6
|
require_once __DIR__ . '/BaseFeature.php';
|
|
7
|
+
require_once __DIR__ . '/../utility/Param.php';
|
|
7
8
|
|
|
8
9
|
class ProjectNameTestFeature extends ProjectNameBaseFeature
|
|
9
10
|
{
|
|
@@ -45,7 +46,7 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
|
|
|
45
46
|
$entity->data = $entity_data;
|
|
46
47
|
|
|
47
48
|
$test_fetcher = function (ProjectNameContext $fctx, string $_fullurl, array $_fetchdef) use ($entity): array {
|
|
48
|
-
$respond = function (int $status, mixed $data, ?array $extra): array {
|
|
49
|
+
$respond = function (int $status, mixed $data, ?array $extra = null): array {
|
|
49
50
|
$out = [
|
|
50
51
|
'status' => $status,
|
|
51
52
|
'statusText' => 'OK',
|
|
@@ -64,53 +65,115 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
|
|
|
64
65
|
$entname = $op->entity;
|
|
65
66
|
$entmap = is_array($entity->data[$entname] ?? null) ? $entity->data[$entname] : [];
|
|
66
67
|
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
// PHP-portable equivalent of TS buildArgs+select: a flat-key
|
|
69
|
+
// filter that matches by exact-equality on each provided key,
|
|
70
|
+
// with alias fallback. Empty match matches all entries — load
|
|
71
|
+
// with empty match returns the first fixture entry (or last
|
|
72
|
+
// create), list returns all entries.
|
|
73
|
+
$find_first = function (array $entmap, $match, $alias) {
|
|
74
|
+
if (!is_array($match) || empty($match)) {
|
|
75
|
+
foreach ($entmap as $e) {
|
|
76
|
+
if (is_array($e)) return $e;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
foreach ($entmap as $e) {
|
|
81
|
+
if (!is_array($e)) continue;
|
|
82
|
+
$ok = true;
|
|
83
|
+
foreach ($match as $k => $v) {
|
|
84
|
+
if ($v === null || $v === '__UNDEFINED__') continue;
|
|
85
|
+
$ev = $e[$k] ?? null;
|
|
86
|
+
if ($ev !== $v) {
|
|
87
|
+
// Try alias key if any
|
|
88
|
+
$ka = is_array($alias) ? ($alias[$k] ?? null) : null;
|
|
89
|
+
$aliased = ($ka !== null) ? ($e[$ka] ?? null) : null;
|
|
90
|
+
if ($aliased !== $v) {
|
|
91
|
+
$ok = false;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
73
95
|
}
|
|
96
|
+
if ($ok) return $e;
|
|
74
97
|
}
|
|
75
98
|
return null;
|
|
76
99
|
};
|
|
77
100
|
|
|
78
|
-
|
|
79
|
-
$
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
101
|
+
$find_all = function (array $entmap, $match, $alias) use ($find_first) {
|
|
102
|
+
if (!is_array($match) || empty($match)) {
|
|
103
|
+
return array_values(array_filter($entmap, 'is_array'));
|
|
104
|
+
}
|
|
105
|
+
$out = [];
|
|
106
|
+
foreach ($entmap as $e) {
|
|
107
|
+
if (!is_array($e)) continue;
|
|
108
|
+
$ok = true;
|
|
109
|
+
foreach ($match as $k => $v) {
|
|
110
|
+
if ($v === null || $v === '__UNDEFINED__') continue;
|
|
111
|
+
$ev = $e[$k] ?? null;
|
|
112
|
+
if ($ev !== $v) {
|
|
113
|
+
$ka = is_array($alias) ? ($alias[$k] ?? null) : null;
|
|
114
|
+
$aliased = ($ka !== null) ? ($e[$ka] ?? null) : null;
|
|
115
|
+
if ($aliased !== $v) { $ok = false; break; }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if ($ok) $out[] = $e;
|
|
119
|
+
}
|
|
120
|
+
return $out;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
$alias = is_object($op) ? ($op->alias ?? null) : \Voxgig\Struct\Struct::getprop($op, 'alias');
|
|
124
|
+
|
|
125
|
+
// For single-entity ops (load, remove) with an empty explicit
|
|
126
|
+
// match, fall back to the id the entity client already knows from a
|
|
127
|
+
// prior create/load (carried in $fctx->match / $fctx->data). This
|
|
128
|
+
// mirrors the TS mock where param() resolves the id from that
|
|
129
|
+
// accumulated state — e.g. `create()` then `remove([])` removes the
|
|
130
|
+
// just-created entity, not an arbitrary fixture.
|
|
131
|
+
$resolve_match = function ($explicit) use ($fctx) {
|
|
132
|
+
if (is_array($explicit) && !empty($explicit)) return $explicit;
|
|
133
|
+
foreach ([$fctx->match, $fctx->data] as $src) {
|
|
134
|
+
$arr = is_array($src) ? $src : (is_object($src) ? (array) $src : []);
|
|
135
|
+
if (isset($arr['id']) && $arr['id'] !== null && $arr['id'] !== '__UNDEFINED__') {
|
|
136
|
+
return ['id' => $arr['id']];
|
|
85
137
|
}
|
|
86
138
|
}
|
|
87
|
-
|
|
139
|
+
return [];
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
if ($op->name === 'load') {
|
|
143
|
+
$ent = $find_first($entmap, $resolve_match($fctx->reqmatch), $alias);
|
|
144
|
+
if ($ent === null) {
|
|
88
145
|
return $respond(404, null, ['statusText' => 'Not found']);
|
|
89
146
|
}
|
|
147
|
+
if (is_array($ent)) unset($ent['$KEY']);
|
|
90
148
|
$out = \Voxgig\Struct\Struct::clone($ent);
|
|
91
|
-
|
|
92
|
-
return $respond(200, $out, null);
|
|
149
|
+
return $respond(200, $out);
|
|
93
150
|
|
|
94
151
|
} elseif ($op->name === 'list') {
|
|
95
|
-
$
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
$out[] = $copy;
|
|
101
|
-
}
|
|
152
|
+
$found = $find_all($entmap, $fctx->reqmatch, $alias);
|
|
153
|
+
$cleaned = [];
|
|
154
|
+
foreach ($found as $e) {
|
|
155
|
+
if (is_array($e)) unset($e['$KEY']);
|
|
156
|
+
$cleaned[] = $e;
|
|
102
157
|
}
|
|
103
|
-
|
|
158
|
+
$out = \Voxgig\Struct\Struct::clone($cleaned);
|
|
159
|
+
return $respond(200, $out);
|
|
104
160
|
|
|
105
161
|
} elseif ($op->name === 'update') {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
162
|
+
// Match the existing entity by id only (or its alias). reqdata
|
|
163
|
+
// also contains the new field values, which would otherwise
|
|
164
|
+
// cause find_first to filter out the entity we want to update.
|
|
165
|
+
$update_match = [];
|
|
166
|
+
if (is_array($fctx->reqdata)) {
|
|
167
|
+
if (array_key_exists('id', $fctx->reqdata)) {
|
|
168
|
+
$update_match['id'] = $fctx->reqdata['id'];
|
|
169
|
+
}
|
|
170
|
+
$id_alias = is_array($alias) ? ($alias['id'] ?? null) : null;
|
|
171
|
+
if ($id_alias !== null && array_key_exists($id_alias, $fctx->reqdata)) {
|
|
172
|
+
$update_match[$id_alias] = $fctx->reqdata[$id_alias];
|
|
111
173
|
}
|
|
112
174
|
}
|
|
113
|
-
|
|
175
|
+
$ent = $find_first($entmap, $update_match, $alias);
|
|
176
|
+
if ($ent === null) {
|
|
114
177
|
return $respond(404, null, ['statusText' => 'Not found']);
|
|
115
178
|
}
|
|
116
179
|
if (is_array($fctx->reqdata)) {
|
|
@@ -118,33 +181,42 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
|
|
|
118
181
|
$ent[$k] = $v;
|
|
119
182
|
}
|
|
120
183
|
}
|
|
121
|
-
$
|
|
122
|
-
$
|
|
184
|
+
$id = is_array($ent) ? ($ent['id'] ?? null) : null;
|
|
185
|
+
if ($id !== null) {
|
|
186
|
+
$entmap[$id] = $ent;
|
|
187
|
+
$entity->data[$entname] = $entmap;
|
|
188
|
+
}
|
|
189
|
+
if (is_array($ent)) unset($ent['$KEY']);
|
|
123
190
|
$out = \Voxgig\Struct\Struct::clone($ent);
|
|
124
|
-
|
|
125
|
-
return $respond(200, $out, null);
|
|
191
|
+
return $respond(200, $out);
|
|
126
192
|
|
|
127
193
|
} elseif ($op->name === 'remove') {
|
|
128
|
-
$
|
|
129
|
-
if ($
|
|
194
|
+
$ent = $find_first($entmap, $resolve_match($fctx->reqmatch), $alias);
|
|
195
|
+
if ($ent === null) {
|
|
196
|
+
return $respond(404, null, ['statusText' => 'Not found']);
|
|
197
|
+
}
|
|
198
|
+
$id = is_array($ent) ? ($ent['id'] ?? null) : null;
|
|
199
|
+
if ($id !== null) {
|
|
130
200
|
unset($entmap[$id]);
|
|
131
201
|
$entity->data[$entname] = $entmap;
|
|
132
202
|
}
|
|
133
|
-
return $respond(200, null
|
|
203
|
+
return $respond(200, null);
|
|
134
204
|
|
|
135
205
|
} elseif ($op->name === 'create') {
|
|
136
|
-
$id = $
|
|
137
|
-
if ($id === null) {
|
|
138
|
-
$id = sprintf('%04x%04x%04x%04x',
|
|
206
|
+
$id = ProjectNameParam::call($fctx, 'id');
|
|
207
|
+
if ($id === null || $id === '__UNDEFINED__') {
|
|
208
|
+
$id = sprintf('%04x%04x%04x%04x',
|
|
209
|
+
random_int(0, 0xFFFF), random_int(0, 0xFFFF),
|
|
210
|
+
random_int(0, 0xFFFF), random_int(0, 0xFFFF));
|
|
139
211
|
}
|
|
140
212
|
|
|
141
213
|
$ent = is_array($fctx->reqdata) ? $fctx->reqdata : [];
|
|
142
214
|
$ent['id'] = $id;
|
|
143
215
|
$entmap[$id] = $ent;
|
|
144
216
|
$entity->data[$entname] = $entmap;
|
|
217
|
+
if (is_array($ent)) unset($ent['$KEY']);
|
|
145
218
|
$out = \Voxgig\Struct\Struct::clone($ent);
|
|
146
|
-
|
|
147
|
-
return $respond(200, $out, null);
|
|
219
|
+
return $respond(200, $out);
|
|
148
220
|
|
|
149
221
|
} else {
|
|
150
222
|
return $respond(404, null, ['statusText' => 'Unknown operation']);
|
|
@@ -153,4 +225,74 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
|
|
|
153
225
|
|
|
154
226
|
$ctx->utility->fetcher = $test_fetcher;
|
|
155
227
|
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Build a structured `$AND` query from the request match/data dict,
|
|
231
|
+
* matching the TS test feature's buildArgs. Mirrors ts/src/feature/test/TestFeature.ts:158-204.
|
|
232
|
+
*
|
|
233
|
+
* For each key in $args that is 'id' OR a required-param key on the
|
|
234
|
+
* current operation point, emit a `$OR` clause matching the key (and
|
|
235
|
+
* its alias, if any) against the supplied value.
|
|
236
|
+
*/
|
|
237
|
+
public function buildArgs(ProjectNameContext $ctx, $op, $args): array
|
|
238
|
+
{
|
|
239
|
+
// If args is empty/missing, return an empty $AND so select() matches
|
|
240
|
+
// every entry — the TS test feature relies on this for empty-match
|
|
241
|
+
// load against fixture entries.
|
|
242
|
+
$keys = is_array($args) ? \Voxgig\Struct\Struct::keysof($args) : [];
|
|
243
|
+
if (empty($keys)) {
|
|
244
|
+
return ['$AND' => []];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
$opname = is_object($op) ? ($op->name ?? null) : (\Voxgig\Struct\Struct::getprop($op, 'name'));
|
|
248
|
+
$entityName = null;
|
|
249
|
+
if (isset($ctx->entity)) {
|
|
250
|
+
$entityName = is_object($ctx->entity)
|
|
251
|
+
? ($ctx->entity->name ?? null)
|
|
252
|
+
: (is_array($ctx->entity) ? ($ctx->entity['name'] ?? null) : null);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Resolve required-param names from the op's last point. Defensive:
|
|
256
|
+
// any missing piece falls back to "no required params".
|
|
257
|
+
$reqd_names = [];
|
|
258
|
+
if (is_string($opname) && is_string($entityName) && isset($ctx->config)) {
|
|
259
|
+
$points = \Voxgig\Struct\Struct::getpath(
|
|
260
|
+
['entity', $entityName, 'op', $opname, 'points'],
|
|
261
|
+
$ctx->config
|
|
262
|
+
);
|
|
263
|
+
$point = \Voxgig\Struct\Struct::getelem($points, -1);
|
|
264
|
+
$params = is_array($point) ? ($point['args']['params'] ?? null) : null;
|
|
265
|
+
if (is_array($params)) {
|
|
266
|
+
foreach ($params as $p) {
|
|
267
|
+
if (is_array($p) && (($p['reqd'] ?? false) === true)) {
|
|
268
|
+
$n = $p['name'] ?? null;
|
|
269
|
+
if ($n !== null) {
|
|
270
|
+
$reqd_names[] = $n;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
$alias = is_object($op) ? ($op->alias ?? null) : \Voxgig\Struct\Struct::getprop($op, 'alias');
|
|
278
|
+
$qand = [];
|
|
279
|
+
|
|
280
|
+
foreach ($keys as $k) {
|
|
281
|
+
$is_id = ($k === 'id');
|
|
282
|
+
$in_reqd = in_array($k, $reqd_names, true);
|
|
283
|
+
if ($is_id || $in_reqd) {
|
|
284
|
+
$v = ProjectNameParam::call($ctx, $k);
|
|
285
|
+
$ka = \Voxgig\Struct\Struct::getprop($alias, $k);
|
|
286
|
+
|
|
287
|
+
$qor = [[$k => $v]];
|
|
288
|
+
if ($ka !== null) {
|
|
289
|
+
$qor[] = [$ka => $v];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
$qand[] = ['$OR' => $qor];
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return ['$AND' => $qand];
|
|
297
|
+
}
|
|
156
298
|
}
|
|
@@ -86,6 +86,68 @@ class ProjectNameTestRunner
|
|
|
86
86
|
}
|
|
87
87
|
return $out;
|
|
88
88
|
}
|
|
89
|
+
|
|
90
|
+
private static $test_control = null;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Load sdk-test-control.json from this test dir; cache. Returns an
|
|
94
|
+
* empty-skip default if the file is missing or invalid.
|
|
95
|
+
*/
|
|
96
|
+
public static function load_test_control(): array
|
|
97
|
+
{
|
|
98
|
+
if (self::$test_control !== null) {
|
|
99
|
+
return self::$test_control;
|
|
100
|
+
}
|
|
101
|
+
$ctrl_path = __DIR__ . '/sdk-test-control.json';
|
|
102
|
+
$default = [
|
|
103
|
+
'version' => 1,
|
|
104
|
+
'test' => ['skip' => [
|
|
105
|
+
'live' => ['direct' => [], 'entityOp' => []],
|
|
106
|
+
'unit' => ['direct' => [], 'entityOp' => []],
|
|
107
|
+
]],
|
|
108
|
+
];
|
|
109
|
+
if (!file_exists($ctrl_path)) {
|
|
110
|
+
self::$test_control = $default;
|
|
111
|
+
return self::$test_control;
|
|
112
|
+
}
|
|
113
|
+
$content = file_get_contents($ctrl_path);
|
|
114
|
+
$parsed = json_decode($content, true);
|
|
115
|
+
self::$test_control = is_array($parsed) ? $parsed : $default;
|
|
116
|
+
return self::$test_control;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Check sdk-test-control.json for a skip entry. Returns [skip, reason].
|
|
121
|
+
*/
|
|
122
|
+
public static function is_control_skipped(string $kind, string $name, string $mode): array
|
|
123
|
+
{
|
|
124
|
+
$ctrl = self::load_test_control();
|
|
125
|
+
$skip = $ctrl['test']['skip'][$mode] ?? [];
|
|
126
|
+
$items = $skip[$kind] ?? [];
|
|
127
|
+
foreach ($items as $item) {
|
|
128
|
+
if ($kind === 'direct' && ($item['test'] ?? null) === $name) {
|
|
129
|
+
return [true, $item['reason'] ?? null];
|
|
130
|
+
}
|
|
131
|
+
if ($kind === 'entityOp') {
|
|
132
|
+
$key = ($item['entity'] ?? '') . '.' . ($item['op'] ?? '');
|
|
133
|
+
if ($key === $name) {
|
|
134
|
+
return [true, $item['reason'] ?? null];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return [false, null];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Per-test live pacing delay (ms); default 500. */
|
|
142
|
+
public static function live_delay_ms(): int
|
|
143
|
+
{
|
|
144
|
+
$ctrl = self::load_test_control();
|
|
145
|
+
$v = $ctrl['test']['live']['delayMs'] ?? null;
|
|
146
|
+
if (is_int($v) && $v >= 0) {
|
|
147
|
+
return $v;
|
|
148
|
+
}
|
|
149
|
+
return 500;
|
|
150
|
+
}
|
|
89
151
|
}
|
|
90
152
|
|
|
91
153
|
// Aliases for test convenience.
|
|
@@ -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 php test runner.",
|
|
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,25 +5,47 @@ declare(strict_types=1);
|
|
|
5
5
|
|
|
6
6
|
class ProjectNameFetcher
|
|
7
7
|
{
|
|
8
|
+
// Default User-Agent — many CDNs (notably Cloudflare) reject requests
|
|
9
|
+
// with PHP's default UA (which file_get_contents doesn't even set),
|
|
10
|
+
// returning 403 before the request reaches the origin. Set a Mozilla-
|
|
11
|
+
// shaped UA so the SDK behaves like every other HTTP client by default.
|
|
12
|
+
// Users can override by passing a User-Agent header in fetchdef.
|
|
13
|
+
public const DEFAULT_USER_AGENT = 'Mozilla/5.0 (compatible; ProjectNameSDK/1.0)';
|
|
14
|
+
|
|
8
15
|
public static function defaultHttpFetch(string $fullurl, array $fetchdef): array
|
|
9
16
|
{
|
|
10
|
-
$method_str = $fetchdef['method'] ?? 'GET';
|
|
17
|
+
$method_str = strtoupper($fetchdef['method'] ?? 'GET');
|
|
11
18
|
$body_str = $fetchdef['body'] ?? null;
|
|
12
19
|
$headers = $fetchdef['headers'] ?? [];
|
|
13
20
|
|
|
14
|
-
$opts = [
|
|
15
|
-
'http' => [
|
|
16
|
-
'method' => strtoupper($method_str),
|
|
17
|
-
'ignore_errors' => true,
|
|
18
|
-
],
|
|
19
|
-
];
|
|
20
|
-
|
|
21
21
|
$header_lines = [];
|
|
22
|
+
$has_ua = false;
|
|
22
23
|
foreach ($headers as $k => $v) {
|
|
23
24
|
if (is_string($v)) {
|
|
25
|
+
if (strcasecmp($k, 'user-agent') === 0) {
|
|
26
|
+
$has_ua = true;
|
|
27
|
+
}
|
|
24
28
|
$header_lines[] = "{$k}: {$v}";
|
|
25
29
|
}
|
|
26
30
|
}
|
|
31
|
+
if (!$has_ua) {
|
|
32
|
+
$header_lines[] = 'User-Agent: ' . self::DEFAULT_USER_AGENT;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Prefer cURL when available — its header capture is reliable across
|
|
36
|
+
// PHP versions, while file_get_contents + custom stream wrappers
|
|
37
|
+
// don't propagate $http_response_header for user-defined wrappers
|
|
38
|
+
// in PHP 8.3+.
|
|
39
|
+
if (function_exists('curl_init')) {
|
|
40
|
+
return self::curlFetch($fullurl, $method_str, $body_str, $header_lines);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
$opts = [
|
|
44
|
+
'http' => [
|
|
45
|
+
'method' => $method_str,
|
|
46
|
+
'ignore_errors' => true,
|
|
47
|
+
],
|
|
48
|
+
];
|
|
27
49
|
|
|
28
50
|
if (is_string($body_str)) {
|
|
29
51
|
$opts['http']['content'] = $body_str;
|
|
@@ -78,6 +100,101 @@ class ProjectNameFetcher
|
|
|
78
100
|
];
|
|
79
101
|
}
|
|
80
102
|
|
|
103
|
+
private static function curlFetch(string $fullurl, string $method, $body_str, array $header_lines): array
|
|
104
|
+
{
|
|
105
|
+
// GET-only guard for live tests — set by pub-live-test. We respect
|
|
106
|
+
// it here at the SDK level since cURL bypasses PHP stream wrappers
|
|
107
|
+
// (so the wrapper-based guard wouldn't see this call).
|
|
108
|
+
if (getenv('VOXGIG_GETONLY_GUARD') === 'TRUE' && $method !== 'GET') {
|
|
109
|
+
fwrite(STDERR, "[GET-ONLY GUARD] blocked {$method} {$fullurl}\n");
|
|
110
|
+
return [
|
|
111
|
+
[
|
|
112
|
+
'status' => 0,
|
|
113
|
+
'statusText' => 'Blocked',
|
|
114
|
+
'headers' => [],
|
|
115
|
+
'json' => function () { return null; },
|
|
116
|
+
'body' => '',
|
|
117
|
+
],
|
|
118
|
+
"GET-only guard blocked {$method} {$fullurl}",
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
$ch = curl_init($fullurl);
|
|
123
|
+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
124
|
+
curl_setopt($ch, CURLOPT_HEADER, false);
|
|
125
|
+
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
|
|
126
|
+
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
|
127
|
+
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
|
|
128
|
+
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
|
129
|
+
if (!empty($header_lines)) {
|
|
130
|
+
curl_setopt($ch, CURLOPT_HTTPHEADER, $header_lines);
|
|
131
|
+
}
|
|
132
|
+
if (is_string($body_str)) {
|
|
133
|
+
curl_setopt($ch, CURLOPT_POSTFIELDS, $body_str);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
$resp_headers = [];
|
|
137
|
+
curl_setopt($ch, CURLOPT_HEADERFUNCTION,
|
|
138
|
+
function ($_ch, $hdr) use (&$resp_headers) {
|
|
139
|
+
$trimmed = trim($hdr);
|
|
140
|
+
if ($trimmed !== '') {
|
|
141
|
+
$resp_headers[] = $trimmed;
|
|
142
|
+
}
|
|
143
|
+
return strlen($hdr);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
$response_body = curl_exec($ch);
|
|
147
|
+
if ($response_body === false) {
|
|
148
|
+
$err = curl_error($ch) ?: 'curl_exec failed';
|
|
149
|
+
curl_close($ch);
|
|
150
|
+
return [
|
|
151
|
+
[
|
|
152
|
+
'status' => 0,
|
|
153
|
+
'statusText' => 'Error',
|
|
154
|
+
'headers' => [],
|
|
155
|
+
'json' => function () { return null; },
|
|
156
|
+
'body' => '',
|
|
157
|
+
],
|
|
158
|
+
$err,
|
|
159
|
+
];
|
|
160
|
+
}
|
|
161
|
+
curl_close($ch);
|
|
162
|
+
|
|
163
|
+
$status = 0;
|
|
164
|
+
$status_text = '';
|
|
165
|
+
$resp_kv = [];
|
|
166
|
+
foreach ($resp_headers as $h) {
|
|
167
|
+
if (preg_match('/^HTTP\/\S+\s+(\d+)\s*(.*)$/i', $h, $m)) {
|
|
168
|
+
$status = (int)$m[1];
|
|
169
|
+
$status_text = trim($m[2]);
|
|
170
|
+
} else {
|
|
171
|
+
$parts = explode(':', $h, 2);
|
|
172
|
+
if (count($parts) === 2) {
|
|
173
|
+
$resp_kv[strtolower(trim($parts[0]))] = trim($parts[1]);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
$json_body = null;
|
|
179
|
+
if ($response_body !== '' && $response_body !== false) {
|
|
180
|
+
$decoded = json_decode($response_body, true);
|
|
181
|
+
if (json_last_error() === JSON_ERROR_NONE) {
|
|
182
|
+
$json_body = $decoded;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return [
|
|
187
|
+
[
|
|
188
|
+
'status' => $status,
|
|
189
|
+
'statusText' => $status_text,
|
|
190
|
+
'headers' => $resp_kv,
|
|
191
|
+
'json' => function () use ($json_body) { return $json_body; },
|
|
192
|
+
'body' => (string)$response_body,
|
|
193
|
+
],
|
|
194
|
+
null,
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
|
|
81
198
|
public static function call(ProjectNameContext $ctx, string $fullurl, array $fetchdef): array
|
|
82
199
|
{
|
|
83
200
|
if ($ctx->client->mode !== 'live') {
|
|
@@ -93,7 +210,13 @@ class ProjectNameFetcher
|
|
|
93
210
|
|
|
94
211
|
$sys_fetch = \Voxgig\Struct\Struct::getpath($options, 'system.fetch');
|
|
95
212
|
|
|
96
|
-
|
|
213
|
+
// Treat null OR an empty stdClass/array as "no fetcher provided" and
|
|
214
|
+
// fall through to the default HTTP fetcher. The options builder
|
|
215
|
+
// sometimes materializes `system.fetch` as an empty stdClass even
|
|
216
|
+
// when the user didn't set one — that's a placeholder, not a value.
|
|
217
|
+
$is_empty_obj = ($sys_fetch instanceof \stdClass) && empty(get_object_vars($sys_fetch));
|
|
218
|
+
$is_empty_arr = is_array($sys_fetch) && empty($sys_fetch);
|
|
219
|
+
if ($sys_fetch === null || $is_empty_obj || $is_empty_arr) {
|
|
97
220
|
return self::defaultHttpFetch($fullurl, $fetchdef);
|
|
98
221
|
}
|
|
99
222
|
if (is_callable($sys_fetch)) {
|
|
@@ -35,6 +35,22 @@ class ProjectNameMakeUrl
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// Append query string from spec.query.
|
|
39
|
+
$qsep = '?';
|
|
40
|
+
$query_items = \Voxgig\Struct\Struct::items($spec->query ?? null);
|
|
41
|
+
if ($query_items) {
|
|
42
|
+
foreach ($query_items as $item) {
|
|
43
|
+
$key = $item[0];
|
|
44
|
+
$val = $item[1];
|
|
45
|
+
if ($val !== null && is_string($key)) {
|
|
46
|
+
$val_str = is_string($val) ? $val : (string)$val;
|
|
47
|
+
$url .= $qsep . \Voxgig\Struct\Struct::escurl($key) . '=' . \Voxgig\Struct\Struct::escurl($val_str);
|
|
48
|
+
$qsep = '&';
|
|
49
|
+
$resmatch[$key] = $val;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
38
54
|
$result->resmatch = $resmatch;
|
|
39
55
|
return [$url, null];
|
|
40
56
|
}
|
|
@@ -18,9 +18,19 @@ class ProjectNamePrepareAuth
|
|
|
18
18
|
|
|
19
19
|
$headers = &$spec->headers;
|
|
20
20
|
$options = $ctx->client->options_map();
|
|
21
|
+
|
|
22
|
+
// Public APIs that need no auth omit the options.auth block entirely.
|
|
23
|
+
if (!isset($options['auth']) || $options['auth'] === null) {
|
|
24
|
+
unset($headers[self::HEADER_AUTH]);
|
|
25
|
+
return [$spec, null];
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
$apikey = \Voxgig\Struct\Struct::getprop($options, self::OPTION_APIKEY, self::NOT_FOUND);
|
|
22
29
|
|
|
23
|
-
if (
|
|
30
|
+
if (
|
|
31
|
+
(is_string($apikey) && ($apikey === self::NOT_FOUND || $apikey === ''))
|
|
32
|
+
|| $apikey === null
|
|
33
|
+
) {
|
|
24
34
|
unset($headers[self::HEADER_AUTH]);
|
|
25
35
|
} else {
|
|
26
36
|
$auth_prefix = \Voxgig\Struct\Struct::getpath($options, 'auth.prefix') ?? '';
|
|
@@ -52,8 +52,21 @@ class ProjectNameTestFeature(ProjectNameBaseFeature):
|
|
|
52
52
|
if not isinstance(entmap, dict):
|
|
53
53
|
entmap = {}
|
|
54
54
|
|
|
55
|
+
# For single-entity ops (load, remove) with an empty explicit
|
|
56
|
+
# match, fall back to the id the entity client already knows from a
|
|
57
|
+
# prior create/load (in fctx.match / fctx.data). Mirrors the TS
|
|
58
|
+
# mock where param() resolves the id from that accumulated state.
|
|
59
|
+
def _resolve_match(explicit):
|
|
60
|
+
if isinstance(explicit, dict) and len(explicit) > 0:
|
|
61
|
+
return explicit
|
|
62
|
+
for src in (getattr(fctx, "match", None), getattr(fctx, "data", None)):
|
|
63
|
+
v = vs.getprop(src, "id") if src is not None else None
|
|
64
|
+
if v is not None and v != "__UNDEFINED__":
|
|
65
|
+
return {"id": v}
|
|
66
|
+
return {}
|
|
67
|
+
|
|
55
68
|
if op.name == "load":
|
|
56
|
-
args = test_self.build_args(fctx, op, fctx.reqmatch)
|
|
69
|
+
args = test_self.build_args(fctx, op, _resolve_match(fctx.reqmatch))
|
|
57
70
|
found = vs.select(entmap, args)
|
|
58
71
|
ent = vs.getelem(found, 0)
|
|
59
72
|
if ent is None:
|
|
@@ -74,9 +87,28 @@ class ProjectNameTestFeature(ProjectNameBaseFeature):
|
|
|
74
87
|
return respond(200, out)
|
|
75
88
|
|
|
76
89
|
elif op.name == "update":
|
|
77
|
-
|
|
90
|
+
# Match the existing entity by id only (or its alias). reqdata
|
|
91
|
+
# also contains the new field values, which would otherwise
|
|
92
|
+
# cause select to filter out the entity we want to update.
|
|
93
|
+
# Falls back to first entity when no match found, mirroring
|
|
94
|
+
# the TS mock.
|
|
95
|
+
update_match = {}
|
|
96
|
+
if isinstance(fctx.reqdata, dict):
|
|
97
|
+
if "id" in fctx.reqdata:
|
|
98
|
+
update_match["id"] = fctx.reqdata["id"]
|
|
99
|
+
alias_map = getattr(op, "alias_map", None)
|
|
100
|
+
if alias_map is not None:
|
|
101
|
+
alias_id = vs.getprop(alias_map, "id")
|
|
102
|
+
if alias_id is not None and alias_id in fctx.reqdata:
|
|
103
|
+
update_match[alias_id] = fctx.reqdata[alias_id]
|
|
104
|
+
args = test_self.build_args(fctx, op, update_match)
|
|
78
105
|
found = vs.select(entmap, args)
|
|
79
106
|
ent = vs.getelem(found, 0)
|
|
107
|
+
if ent is None and isinstance(entmap, dict):
|
|
108
|
+
for e in entmap.values():
|
|
109
|
+
if isinstance(e, dict):
|
|
110
|
+
ent = e
|
|
111
|
+
break
|
|
80
112
|
if ent is None:
|
|
81
113
|
return respond(404, None, {"statusText": "Not found"})
|
|
82
114
|
if isinstance(ent, dict):
|
|
@@ -89,7 +121,7 @@ class ProjectNameTestFeature(ProjectNameBaseFeature):
|
|
|
89
121
|
return respond(200, out)
|
|
90
122
|
|
|
91
123
|
elif op.name == "remove":
|
|
92
|
-
args = test_self.build_args(fctx, op, fctx.reqmatch)
|
|
124
|
+
args = test_self.build_args(fctx, op, _resolve_match(fctx.reqmatch))
|
|
93
125
|
found = vs.select(entmap, args)
|
|
94
126
|
ent = vs.getelem(found, 0)
|
|
95
127
|
if ent is None:
|