@voxgig/sdkgen 4.0.1 → 4.2.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/package.json +1 -1
- package/project/.sdk/src/cmp/scala/TestDirect_scala.ts +1 -1
- package/project/.sdk/src/cmp/scala/TestEntity_scala.ts +1 -1
- package/project/.sdk/tm/c/feature/test.c +50 -12
- package/project/.sdk/tm/clojure/src/sdk/features.clj +21 -3
- package/project/.sdk/tm/cpp/Makefile +11 -1
- package/project/.sdk/tm/cpp/feature/test.hpp +34 -11
- package/project/.sdk/tm/csharp/feature/TestFeature.cs +28 -2
- package/project/.sdk/tm/dart/lib/feature/test/TestFeature.dart +38 -10
- package/project/.sdk/tm/elixir/lib/projectname/feature/test.ex +40 -13
- package/project/.sdk/tm/java/feature/TestFeature.java +46 -12
- package/project/.sdk/tm/kotlin/feature/TestFeature.kt +29 -11
- package/project/.sdk/tm/lean/src/SdkRuntime.lean +19 -1
- package/project/.sdk/tm/lean/src/SdkUtility.lean +2 -2
- package/project/.sdk/tm/ocaml/sdk_features.ml +31 -12
- package/project/.sdk/tm/perl/feature/test_feature.pm +34 -12
- package/project/.sdk/tm/py-data/test/conftest.py +24 -0
- package/project/.sdk/tm/rust/feature/test.rs +41 -16
- package/project/.sdk/tm/scala/feature/TestFeature.scala +35 -12
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/TestFeature.swift +33 -12
- package/project/.sdk/tm/zig/feature/test.zig +39 -12
- package/project/sdkgen-package.json +1 -1
package/bin/voxgig-sdkgen
CHANGED
package/package.json
CHANGED
|
@@ -118,7 +118,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
|
|
|
118
118
|
import java.util.{ArrayList, LinkedHashMap, List => JList, Map => JMap}
|
|
119
119
|
import java.util.function.{BiFunction, Supplier}
|
|
120
120
|
|
|
121
|
-
import ${scalapackage}.core.{Helpers, ${SDK}}
|
|
121
|
+
import ${scalapackage}.core.{Helpers, SdkEntity, ${SDK}}
|
|
122
122
|
|
|
123
123
|
object ${EntityName}DirectTest {
|
|
124
124
|
|
|
@@ -90,7 +90,7 @@ const TestEntity = cmp(function TestEntity(props: any) {
|
|
|
90
90
|
|
|
91
91
|
import java.util.{ArrayList, LinkedHashMap, List => JList, Map => JMap}
|
|
92
92
|
|
|
93
|
-
import ${scalapackage}.core.{Helpers, ${SDK}}
|
|
93
|
+
import ${scalapackage}.core.{Helpers, SdkEntity, ${SDK}}
|
|
94
94
|
import ${scalapackage}.utility.struct.Struct
|
|
95
95
|
|
|
96
96
|
object ${EntityName}EntityTest {
|
|
@@ -10,10 +10,48 @@
|
|
|
10
10
|
#include <stdlib.h>
|
|
11
11
|
#include <string.h>
|
|
12
12
|
|
|
13
|
+
// THE MOCK HAS TO AGREE WITH THE MODEL.
|
|
14
|
+
//
|
|
15
|
+
// A point carrying `transform.res: `body.item`` describes an API that answers
|
|
16
|
+
// {"item": {...}}, and the response transform unwraps that key on the way
|
|
17
|
+
// back. Handing back the bare payload means the transform unwraps a property
|
|
18
|
+
// that is not there and the caller gets nothing — a mock that only ever
|
|
19
|
+
// simulates APIs whose responses happen to be unwrapped.
|
|
20
|
+
//
|
|
21
|
+
// univec's list op declares `body.data`, so every list returned zero items
|
|
22
|
+
// while the fixture plainly held two. Mirrors the go/ts/lua/php mocks, which
|
|
23
|
+
// already wrap; rust, c and zig were the three that did not.
|
|
24
|
+
static voxgig_value* envelope(Context* ctx, voxgig_value* data) {
|
|
25
|
+
if (v_is_noval(data) || v_is_null(data)) {
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
voxgig_value* tm = getp(ctx->point, "transform");
|
|
29
|
+
const char* spec = get_str(tm, "res");
|
|
30
|
+
if (NULL == spec) {
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
// Exactly `body.<key>`; a deeper path is not an envelope this mock can
|
|
34
|
+
// synthesise, so it is left alone rather than guessed at.
|
|
35
|
+
size_t n = strlen(spec);
|
|
36
|
+
if (n < 8 || 0 != strncmp(spec, "`body.", 6) || '`' != spec[n - 1]) {
|
|
37
|
+
return data;
|
|
38
|
+
}
|
|
39
|
+
size_t inner_len = n - 7;
|
|
40
|
+
if (0 == inner_len || NULL != memchr(spec + 6, '.', inner_len)) {
|
|
41
|
+
return data;
|
|
42
|
+
}
|
|
43
|
+
char* inner = (char*)malloc(inner_len + 1);
|
|
44
|
+
memcpy(inner, spec + 6, inner_len);
|
|
45
|
+
inner[inner_len] = '\0';
|
|
46
|
+
voxgig_value* out = cmap(1, inner, data);
|
|
47
|
+
free(inner);
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
13
51
|
// respond builds a transport-shaped response the result pipeline understands.
|
|
14
|
-
static voxgig_value* respond(int64_t status, voxgig_value* data) {
|
|
52
|
+
static voxgig_value* respond(Context* ctx, int64_t status, voxgig_value* data) {
|
|
15
53
|
return cmap(4, "status", v_num((double)status), "statusText", v_str("OK"), "json",
|
|
16
|
-
json_thunk(data), "body", v_str("not-used"));
|
|
54
|
+
json_thunk(envelope(ctx, data)), "body", v_str("not-used"));
|
|
17
55
|
}
|
|
18
56
|
|
|
19
57
|
// For single-entity ops (load, remove) with an empty explicit match, fall
|
|
@@ -109,19 +147,19 @@ static voxgig_value* test_fetch(voxgig_value* entity, Context* ctx, const char*
|
|
|
109
147
|
voxgig_value* found = voxgig_select(entmap, args);
|
|
110
148
|
voxgig_value* ent = voxgig_getelem(found, v_int(0), NULL);
|
|
111
149
|
if (v_is_noval(ent) || v_is_null(ent)) {
|
|
112
|
-
voxgig_value* r = respond(404, v_undef());
|
|
150
|
+
voxgig_value* r = respond(ctx, 404, v_undef());
|
|
113
151
|
setp(r, "statusText", v_str("Not found"));
|
|
114
152
|
return r;
|
|
115
153
|
}
|
|
116
154
|
voxgig_delprop(ent, v_str("$KEY"));
|
|
117
|
-
return respond(200, voxgig_clone(ent));
|
|
155
|
+
return respond(ctx, 200, voxgig_clone(ent));
|
|
118
156
|
}
|
|
119
157
|
|
|
120
158
|
if (strcmp(opname, "list") == 0) {
|
|
121
159
|
voxgig_value* args = build_args(ctx, ctx->reqmatch);
|
|
122
160
|
voxgig_value* found = voxgig_select(entmap, args);
|
|
123
161
|
if (v_is_noval(found) || v_is_null(found)) {
|
|
124
|
-
voxgig_value* r = respond(404, v_undef());
|
|
162
|
+
voxgig_value* r = respond(ctx, 404, v_undef());
|
|
125
163
|
setp(r, "statusText", v_str("Not found"));
|
|
126
164
|
return r;
|
|
127
165
|
}
|
|
@@ -131,7 +169,7 @@ static voxgig_value* test_fetch(voxgig_value* entity, Context* ctx, const char*
|
|
|
131
169
|
voxgig_delprop(l->items[i], v_str("$KEY"));
|
|
132
170
|
}
|
|
133
171
|
}
|
|
134
|
-
return respond(200, voxgig_clone(found));
|
|
172
|
+
return respond(ctx, 200, voxgig_clone(found));
|
|
135
173
|
}
|
|
136
174
|
|
|
137
175
|
if (strcmp(opname, "update") == 0) {
|
|
@@ -168,7 +206,7 @@ static voxgig_value* test_fetch(voxgig_value* entity, Context* ctx, const char*
|
|
|
168
206
|
}
|
|
169
207
|
}
|
|
170
208
|
if (v_is_noval(ent) || v_is_null(ent)) {
|
|
171
|
-
voxgig_value* r = respond(404, v_undef());
|
|
209
|
+
voxgig_value* r = respond(ctx, 404, v_undef());
|
|
172
210
|
setp(r, "statusText", v_str("Not found"));
|
|
173
211
|
return r;
|
|
174
212
|
}
|
|
@@ -179,7 +217,7 @@ static voxgig_value* test_fetch(voxgig_value* entity, Context* ctx, const char*
|
|
|
179
217
|
}
|
|
180
218
|
}
|
|
181
219
|
voxgig_delprop(ent, v_str("$KEY"));
|
|
182
|
-
return respond(200, voxgig_clone(ent));
|
|
220
|
+
return respond(ctx, 200, voxgig_clone(ent));
|
|
183
221
|
}
|
|
184
222
|
|
|
185
223
|
if (strcmp(opname, "remove") == 0) {
|
|
@@ -192,7 +230,7 @@ static voxgig_value* test_fetch(voxgig_value* entity, Context* ctx, const char*
|
|
|
192
230
|
voxgig_value* id = getp(ent, "id");
|
|
193
231
|
voxgig_delprop(entmap, id);
|
|
194
232
|
}
|
|
195
|
-
return respond(200, v_undef());
|
|
233
|
+
return respond(ctx, 200, v_undef());
|
|
196
234
|
}
|
|
197
235
|
|
|
198
236
|
if (strcmp(opname, "create") == 0) {
|
|
@@ -216,12 +254,12 @@ static voxgig_value* test_fetch(voxgig_value* entity, Context* ctx, const char*
|
|
|
216
254
|
setp(entmap, voxgig_as_string(id), ent);
|
|
217
255
|
}
|
|
218
256
|
voxgig_delprop(ent, v_str("$KEY"));
|
|
219
|
-
return respond(200, voxgig_clone(ent));
|
|
257
|
+
return respond(ctx, 200, voxgig_clone(ent));
|
|
220
258
|
}
|
|
221
|
-
return respond(200, ent);
|
|
259
|
+
return respond(ctx, 200, ent);
|
|
222
260
|
}
|
|
223
261
|
|
|
224
|
-
voxgig_value* r = respond(404, v_undef());
|
|
262
|
+
voxgig_value* r = respond(ctx, 404, v_undef());
|
|
225
263
|
setp(r, "statusText", v_str("Unknown operation"));
|
|
226
264
|
return r;
|
|
227
265
|
}
|
|
@@ -104,9 +104,27 @@
|
|
|
104
104
|
val))
|
|
105
105
|
(let [test-fetcher
|
|
106
106
|
(fn [fctx _fullurl _fetchdef]
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
;; THE MOCK HAS TO AGREE WITH THE MODEL. A point carrying
|
|
108
|
+
;; `transform.res: `body.item`` describes an API that answers
|
|
109
|
+
;; {"item": {...}}, and the response transform unwraps that key
|
|
110
|
+
;; on the way back. Returning the bare payload means the
|
|
111
|
+
;; transform unwraps a property that is not there and the caller
|
|
112
|
+
;; gets nothing. Mirrors the go/ts/lua/php mocks. `fctx` is in
|
|
113
|
+
;; scope here, so the point is the one being served.
|
|
114
|
+
(let [envelope (fn [data]
|
|
115
|
+
(let [tm (vs/getprop (core/oget fctx :point) "transform")
|
|
116
|
+
spec (vs/getprop tm "res")]
|
|
117
|
+
;; Exactly `body.<key>`; a deeper path is not an
|
|
118
|
+
;; envelope this mock can synthesise.
|
|
119
|
+
(if (and (some? data) (string? spec))
|
|
120
|
+
(if-let [m (re-matches #"`body\.([^`.]+)`" spec)]
|
|
121
|
+
(vs/jm (second m) data)
|
|
122
|
+
data)
|
|
123
|
+
data)))
|
|
124
|
+
respond (fn [status data extra]
|
|
125
|
+
(let [payload (envelope data)
|
|
126
|
+
out (vs/jm "status" status "statusText" "OK"
|
|
127
|
+
"json" (fn [] payload) "body" "not-used")]
|
|
110
128
|
(when extra (doseq [item (or (vs/items extra) [])]
|
|
111
129
|
(.put ^java.util.Map out (vs/getprop item 0) (vs/getprop item 1))))
|
|
112
130
|
[out nil]))
|
|
@@ -10,6 +10,16 @@ CXXFLAGS ?= -std=c++17 -O0 -g -Wall -Wextra -Wno-unused-parameter -pthread
|
|
|
10
10
|
TEST_SRCS := $(wildcard test/*.cpp)
|
|
11
11
|
TEST_BINS := $(TEST_SRCS:.cpp=.out)
|
|
12
12
|
|
|
13
|
+
# REBUILD WHEN A HEADER CHANGES. The runtime is header-only, so a test binary
|
|
14
|
+
# depends on far more than its own .cpp — but the pattern rule below names
|
|
15
|
+
# only that .cpp, so make happily reused a stale .out after every change to
|
|
16
|
+
# core/, feature/, entity/ or utility/. A real fix to the mock transport read
|
|
17
|
+
# as "still failing" through several rebuilds because the binary under test
|
|
18
|
+
# predated it. Listing the headers is cheap and makes `make test` mean what it
|
|
19
|
+
# says.
|
|
20
|
+
SDK_HDRS := $(wildcard core/*.hpp entity/*.hpp feature/*.hpp \
|
|
21
|
+
utility/*.hpp utility/*/*.hpp *.hpp)
|
|
22
|
+
|
|
13
23
|
.PHONY: test build clean
|
|
14
24
|
|
|
15
25
|
build: $(TEST_BINS)
|
|
@@ -23,7 +33,7 @@ test: build
|
|
|
23
33
|
if [ $$fail -ne 0 ]; then echo "SOME TESTS FAILED"; exit 1; fi; \
|
|
24
34
|
echo "ALL TESTS PASSED"
|
|
25
35
|
|
|
26
|
-
test/%.out: test/%.cpp
|
|
36
|
+
test/%.out: test/%.cpp $(SDK_HDRS)
|
|
27
37
|
$(CXX) $(CXXFLAGS) $< -o $@
|
|
28
38
|
|
|
29
39
|
clean:
|
|
@@ -57,11 +57,34 @@ public:
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
private:
|
|
60
|
-
|
|
60
|
+
// THE MOCK HAS TO AGREE WITH THE MODEL. A point carrying
|
|
61
|
+
// `transform.res: `body.item`` describes an API that answers {"item": {...}}
|
|
62
|
+
// and the response transform unwraps that key on the way back. Returning the
|
|
63
|
+
// bare payload means the transform unwraps a property that is not there and
|
|
64
|
+
// the caller gets nothing. Mirrors the go/ts/lua/php mocks.
|
|
65
|
+
Value envelope(CtxPtr ctx, const Value& data) {
|
|
66
|
+
if (is_nullish(data) || !ctx) return data;
|
|
67
|
+
Value tm = getp(ctx->point, "transform");
|
|
68
|
+
Value restf = getp(tm, "res");
|
|
69
|
+
if (!restf.is_string()) return data;
|
|
70
|
+
std::string spec = restf.as_string();
|
|
71
|
+
// Exactly `body.<key>`; a deeper path is not an envelope this mock can
|
|
72
|
+
// synthesise, so it is left alone rather than guessed at.
|
|
73
|
+
if (spec.size() < 8) return data;
|
|
74
|
+
if (0 != spec.compare(0, 6, "`body.")) return data;
|
|
75
|
+
if ('`' != spec[spec.size() - 1]) return data;
|
|
76
|
+
std::string inner = spec.substr(6, spec.size() - 7);
|
|
77
|
+
if (inner.empty() || std::string::npos != inner.find('.')) return data;
|
|
78
|
+
Value wrapped = vmap();
|
|
79
|
+
map_put(wrapped, inner, data);
|
|
80
|
+
return wrapped;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
Value respond(CtxPtr ctx, int status, const Value& data, const Value& extra) {
|
|
61
84
|
Value out = vmap();
|
|
62
85
|
map_put(out, "status", Value(status));
|
|
63
86
|
map_put(out, "statusText", Value("OK"));
|
|
64
|
-
map_put(out, "json", json_thunk(data));
|
|
87
|
+
map_put(out, "json", json_thunk(envelope(ctx, data)));
|
|
65
88
|
map_put(out, "body", Value("not-used"));
|
|
66
89
|
if (extra.is_map()) {
|
|
67
90
|
for (const auto& kv : *extra.as_map()) map_put(out, kv.first, kv.second);
|
|
@@ -100,10 +123,10 @@ private:
|
|
|
100
123
|
Value args = buildArgs(ctx, op, resolveMatch(ctx, ctx->reqmatch));
|
|
101
124
|
std::vector<Value> found = Struct::select(entmap, args);
|
|
102
125
|
Value ent = found.empty() ? Value::undef() : found[0];
|
|
103
|
-
if (is_nullish(ent)) return respond(404, Value(nullptr), extra1("statusText", Value("Not found")));
|
|
126
|
+
if (is_nullish(ent)) return respond(ctx, 404, Value(nullptr), extra1("statusText", Value("Not found")));
|
|
104
127
|
Struct::delprop(ent, Value("$KEY"));
|
|
105
128
|
Value out = Struct::clone(ent);
|
|
106
|
-
return respond(200, out, Value::undef());
|
|
129
|
+
return respond(ctx, 200, out, Value::undef());
|
|
107
130
|
} else if (op->name == "list") {
|
|
108
131
|
Value args = buildArgs(ctx, op, ctx->reqmatch);
|
|
109
132
|
std::vector<Value> found = Struct::select(entmap, args);
|
|
@@ -113,7 +136,7 @@ private:
|
|
|
113
136
|
outlist.as_list()->push_back(item);
|
|
114
137
|
}
|
|
115
138
|
Value out = Struct::clone(outlist);
|
|
116
|
-
return respond(200, out, Value::undef());
|
|
139
|
+
return respond(ctx, 200, out, Value::undef());
|
|
117
140
|
} else if (op->name == "update") {
|
|
118
141
|
Value updateMatch = vmap();
|
|
119
142
|
if (ctx->reqdata.is_map()) {
|
|
@@ -141,13 +164,13 @@ private:
|
|
|
141
164
|
if (kv.second.is_map()) { ent = kv.second; break; }
|
|
142
165
|
}
|
|
143
166
|
}
|
|
144
|
-
if (is_nullish(ent)) return respond(404, Value(nullptr), extra1("statusText", Value("Not found")));
|
|
167
|
+
if (is_nullish(ent)) return respond(ctx, 404, Value(nullptr), extra1("statusText", Value("Not found")));
|
|
145
168
|
if (ent.is_map() && ctx->reqdata.is_map()) {
|
|
146
169
|
for (const auto& kv : *ctx->reqdata.as_map()) map_put(ent, kv.first, kv.second);
|
|
147
170
|
}
|
|
148
171
|
Struct::delprop(ent, Value("$KEY"));
|
|
149
172
|
Value out = Struct::clone(ent);
|
|
150
|
-
return respond(200, out, Value::undef());
|
|
173
|
+
return respond(ctx, 200, out, Value::undef());
|
|
151
174
|
} else if (op->name == "remove") {
|
|
152
175
|
Value args = buildArgs(ctx, op, resolveMatch(ctx, ctx->reqmatch));
|
|
153
176
|
std::vector<Value> found = Struct::select(entmap, args);
|
|
@@ -156,7 +179,7 @@ private:
|
|
|
156
179
|
Value id = getp(ent, "id", Value(nullptr));
|
|
157
180
|
Struct::delprop(entmap, id);
|
|
158
181
|
}
|
|
159
|
-
return respond(200, Value(nullptr), Value::undef());
|
|
182
|
+
return respond(ctx, 200, Value(nullptr), Value::undef());
|
|
160
183
|
} else if (op->name == "create") {
|
|
161
184
|
buildArgs(ctx, op, ctx->reqdata);
|
|
162
185
|
Value id = ctx->utility->param(ctx, Value("id"));
|
|
@@ -177,12 +200,12 @@ private:
|
|
|
177
200
|
if (id.is_string()) map_put(entmap, id.as_string(), ent);
|
|
178
201
|
Struct::delprop(ent, Value("$KEY"));
|
|
179
202
|
Value out = Struct::clone(ent);
|
|
180
|
-
return respond(200, out, Value::undef());
|
|
203
|
+
return respond(ctx, 200, out, Value::undef());
|
|
181
204
|
}
|
|
182
|
-
return respond(200, ent, Value::undef());
|
|
205
|
+
return respond(ctx, 200, ent, Value::undef());
|
|
183
206
|
}
|
|
184
207
|
|
|
185
|
-
return respond(404, Value(nullptr), extra1("statusText", Value("Unknown operation")));
|
|
208
|
+
return respond(ctx, 404, Value(nullptr), extra1("statusText", Value("Unknown operation")));
|
|
186
209
|
}
|
|
187
210
|
|
|
188
211
|
// ---- net simulation over the mock -----------------------------------
|
|
@@ -45,14 +45,40 @@ public class TestFeature : BaseFeature
|
|
|
45
45
|
|
|
46
46
|
FetcherFunc testFetcher = (ctx2, _fullurl, _fetchdef) =>
|
|
47
47
|
{
|
|
48
|
-
|
|
48
|
+
// THE MOCK HAS TO AGREE WITH THE MODEL. A point carrying
|
|
49
|
+
// `transform.res: `body.item`` describes an API that answers
|
|
50
|
+
// {"item": {...}}, and the response transform unwraps that key on
|
|
51
|
+
// the way back. Returning the bare payload means the transform
|
|
52
|
+
// unwraps a property that is not there and the caller gets
|
|
53
|
+
// nothing. Mirrors the go/ts/lua/php mocks.
|
|
54
|
+
//
|
|
55
|
+
// NOT `static`: it closes over ctx2 to read the point, which is
|
|
56
|
+
// how go's mock does it too.
|
|
57
|
+
object? Envelope(object? data)
|
|
58
|
+
{
|
|
59
|
+
if (data == null || ctx2.Point == null) { return data; }
|
|
60
|
+
var tm = StructUtils.GetProp(ctx2.Point, "transform");
|
|
61
|
+
if (StructUtils.GetProp(tm, "res") is not string spec) { return data; }
|
|
62
|
+
// Exactly `body.<key>`; a deeper path is not an envelope this
|
|
63
|
+
// mock can synthesise, so it is left alone.
|
|
64
|
+
if (!spec.StartsWith("`body.") || !spec.EndsWith("`") || spec.Length < 8)
|
|
65
|
+
{
|
|
66
|
+
return data;
|
|
67
|
+
}
|
|
68
|
+
var inner = spec.Substring(6, spec.Length - 7);
|
|
69
|
+
if (inner.Length == 0 || inner.Contains('.')) { return data; }
|
|
70
|
+
return new Dictionary<string, object?> { [inner] = data };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
Dictionary<string, object?> Respond(int status, object? data,
|
|
49
74
|
Dictionary<string, object?>? extra)
|
|
50
75
|
{
|
|
76
|
+
var payload = Envelope(data);
|
|
51
77
|
var res = new Dictionary<string, object?>
|
|
52
78
|
{
|
|
53
79
|
["status"] = status,
|
|
54
80
|
["statusText"] = "OK",
|
|
55
|
-
["json"] = (Func<object?>)(() =>
|
|
81
|
+
["json"] = (Func<object?>)(() => payload),
|
|
56
82
|
["body"] = "not-used",
|
|
57
83
|
};
|
|
58
84
|
if (extra != null)
|
|
@@ -38,12 +38,40 @@ class TestFeature extends BaseFeature {
|
|
|
38
38
|
|
|
39
39
|
final self = this;
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
// THE MOCK HAS TO AGREE WITH THE MODEL. A point carrying
|
|
42
|
+
// `transform.res: `body.item`` describes an API that answers
|
|
43
|
+
// {"item": {...}}, and the response transform unwraps that key on the way
|
|
44
|
+
// back. Returning the bare payload means the transform unwraps a property
|
|
45
|
+
// that is not there and the caller gets nothing. Mirrors the go/ts/lua/php
|
|
46
|
+
// mocks.
|
|
47
|
+
dynamic envelope(dynamic fctx, dynamic data) {
|
|
48
|
+
if (null == data || null == fctx || null == fctx.point) {
|
|
49
|
+
return data;
|
|
50
|
+
}
|
|
51
|
+
// dart wraps the point in a Point OBJECT (Context._aspoint), so its
|
|
52
|
+
// fields are typed properties, not map keys — vs.getprop on it returns
|
|
53
|
+
// null and the envelope silently never applies.
|
|
54
|
+
final tm = fctx.point.transform;
|
|
55
|
+
final spec = vs.getprop(tm, 'res');
|
|
56
|
+
if (spec is! String) {
|
|
57
|
+
return data;
|
|
58
|
+
}
|
|
59
|
+
// Exactly `body.<key>`; a deeper path is not an envelope this mock can
|
|
60
|
+
// synthesise, so it is left alone rather than guessed at.
|
|
61
|
+
final m = RegExp(r'^`body\.([^`.]+)`$').firstMatch(spec);
|
|
62
|
+
if (null == m) {
|
|
63
|
+
return data;
|
|
64
|
+
}
|
|
65
|
+
return <String, dynamic>{m.group(1)!: data};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
dynamic respond(dynamic fctx, int status, [dynamic data, dynamic res]) {
|
|
69
|
+
final payload = envelope(fctx, data);
|
|
42
70
|
final out = vs.merge([
|
|
43
71
|
<String, dynamic>{
|
|
44
72
|
'status': status,
|
|
45
73
|
'statusText': 'OK',
|
|
46
|
-
'json': () =>
|
|
74
|
+
'json': () => payload,
|
|
47
75
|
'body': 'not-used',
|
|
48
76
|
},
|
|
49
77
|
vs.getdef(res, {}),
|
|
@@ -66,30 +94,30 @@ class TestFeature extends BaseFeature {
|
|
|
66
94
|
final found = vs.select(entmap, args);
|
|
67
95
|
final ent = vs.getelem(found, 0);
|
|
68
96
|
if (null == ent) {
|
|
69
|
-
return respond(404, null, {'statusText': S_NOT_FOUND});
|
|
97
|
+
return respond(fctx, 404, null, {'statusText': S_NOT_FOUND});
|
|
70
98
|
} else {
|
|
71
99
|
vs.delprop(ent, r'$KEY');
|
|
72
100
|
final out = vs.clone(ent);
|
|
73
|
-
return respond(200, out);
|
|
101
|
+
return respond(fctx, 200, out);
|
|
74
102
|
}
|
|
75
103
|
} else if ('list' == op.name) {
|
|
76
104
|
final args = self.buildArgs(fctx, op, fctx.reqmatch);
|
|
77
105
|
final found = vs.select(entmap, args);
|
|
78
106
|
if (null == found) {
|
|
79
|
-
return respond(404, null, {'statusText': S_NOT_FOUND});
|
|
107
|
+
return respond(fctx, 404, null, {'statusText': S_NOT_FOUND});
|
|
80
108
|
} else {
|
|
81
109
|
for (final ent in found) {
|
|
82
110
|
vs.delprop(ent, r'$KEY');
|
|
83
111
|
}
|
|
84
112
|
final out = vs.clone(found);
|
|
85
|
-
return respond(200, out);
|
|
113
|
+
return respond(fctx, 200, out);
|
|
86
114
|
}
|
|
87
115
|
} else if ('update' == op.name) {
|
|
88
116
|
final args = self.buildArgs(fctx, op, fctx.reqdata);
|
|
89
117
|
final found = vs.select(entmap, args);
|
|
90
118
|
final ent = vs.getelem(found, 0);
|
|
91
119
|
if (null == ent) {
|
|
92
|
-
return respond(404, null, {'statusText': S_NOT_FOUND});
|
|
120
|
+
return respond(fctx, 404, null, {'statusText': S_NOT_FOUND});
|
|
93
121
|
} else {
|
|
94
122
|
// Dart's single null stands in for the donor's undefined: merge
|
|
95
123
|
// must not overwrite stored values with absent ones.
|
|
@@ -104,7 +132,7 @@ class TestFeature extends BaseFeature {
|
|
|
104
132
|
vs.merge([ent, upddata]);
|
|
105
133
|
vs.delprop(ent, r'$KEY');
|
|
106
134
|
final out = vs.clone(ent);
|
|
107
|
-
return respond(200, out);
|
|
135
|
+
return respond(fctx, 200, out);
|
|
108
136
|
}
|
|
109
137
|
} else if ('remove' == op.name) {
|
|
110
138
|
final args = self.buildArgs(fctx, op, fctx.reqmatch);
|
|
@@ -115,7 +143,7 @@ class TestFeature extends BaseFeature {
|
|
|
115
143
|
if (null != ent) {
|
|
116
144
|
vs.delprop(entmap, vs.getprop(ent, 'id'));
|
|
117
145
|
}
|
|
118
|
-
return respond(200);
|
|
146
|
+
return respond(fctx, 200);
|
|
119
147
|
} else if ('create' == op.name) {
|
|
120
148
|
self.buildArgs(fctx, op, fctx.reqdata);
|
|
121
149
|
dynamic id = param(fctx, 'id');
|
|
@@ -130,7 +158,7 @@ class TestFeature extends BaseFeature {
|
|
|
130
158
|
vs.setprop(entmap, id, ent);
|
|
131
159
|
vs.delprop(ent, r'$KEY');
|
|
132
160
|
final out = vs.clone(ent);
|
|
133
|
-
return respond(200, out);
|
|
161
|
+
return respond(fctx, 200, out);
|
|
134
162
|
}
|
|
135
163
|
|
|
136
164
|
return null;
|
|
@@ -47,12 +47,39 @@ defmodule ProjectName.Feature.Test do
|
|
|
47
47
|
nil
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
# THE MOCK HAS TO AGREE WITH THE MODEL. A point carrying
|
|
51
|
+
# `transform.res: `body.item`` describes an API that answers {"item": {...}}
|
|
52
|
+
# and the response transform unwraps that key on the way back. Returning the
|
|
53
|
+
# bare payload means the transform unwraps a property that is not there and
|
|
54
|
+
# the caller gets nothing. Mirrors the go/ts/lua/php mocks.
|
|
55
|
+
defp envelope(fctx, data) do
|
|
56
|
+
spec = S.getprop(S.getprop(S.getprop(fctx, "point"), "transform"), "res")
|
|
57
|
+
|
|
58
|
+
# Exactly `body.<key>`; a deeper path is not an envelope this mock can
|
|
59
|
+
# synthesise, so it is left alone rather than guessed at.
|
|
60
|
+
case {data, spec} do
|
|
61
|
+
{nil, _} ->
|
|
62
|
+
data
|
|
63
|
+
|
|
64
|
+
{_, s} when is_binary(s) ->
|
|
65
|
+
case Regex.run(~r/^`body\.([^`.]+)`$/, s) do
|
|
66
|
+
[_, inner] -> S.jm([inner, data])
|
|
67
|
+
_ -> data
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
_ ->
|
|
71
|
+
data
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
defp respond(fctx, status, data, extra \\ nil) do
|
|
76
|
+
payload = envelope(fctx, data)
|
|
77
|
+
|
|
51
78
|
out =
|
|
52
79
|
S.jm([
|
|
53
80
|
"status", status,
|
|
54
81
|
"statusText", "OK",
|
|
55
|
-
"json", fn ->
|
|
82
|
+
"json", fn -> payload end,
|
|
56
83
|
"body", "not-used"
|
|
57
84
|
])
|
|
58
85
|
|
|
@@ -87,10 +114,10 @@ defmodule ProjectName.Feature.Test do
|
|
|
87
114
|
ent = S.getelem(found, 0)
|
|
88
115
|
|
|
89
116
|
if ent == nil do
|
|
90
|
-
respond(404, nil, S.jm(["statusText", "Not found"]))
|
|
117
|
+
respond(fctx, 404, nil, S.jm(["statusText", "Not found"]))
|
|
91
118
|
else
|
|
92
119
|
S.delprop(ent, "$KEY")
|
|
93
|
-
respond(200, S.clone(ent))
|
|
120
|
+
respond(fctx, 200, S.clone(ent))
|
|
94
121
|
end
|
|
95
122
|
|
|
96
123
|
"list" ->
|
|
@@ -98,13 +125,13 @@ defmodule ProjectName.Feature.Test do
|
|
|
98
125
|
found = S.select(entmap, args)
|
|
99
126
|
|
|
100
127
|
if found == nil do
|
|
101
|
-
respond(404, nil, S.jm(["statusText", "Not found"]))
|
|
128
|
+
respond(fctx, 404, nil, S.jm(["statusText", "Not found"]))
|
|
102
129
|
else
|
|
103
130
|
if S.islist(found) and S.size(found) > 0 do
|
|
104
131
|
Enum.each(0..(S.size(found) - 1), fn i -> S.delprop(S.getelem(found, i), "$KEY") end)
|
|
105
132
|
end
|
|
106
133
|
|
|
107
|
-
respond(200, S.clone(found))
|
|
134
|
+
respond(fctx, 200, S.clone(found))
|
|
108
135
|
end
|
|
109
136
|
|
|
110
137
|
"update" ->
|
|
@@ -143,14 +170,14 @@ defmodule ProjectName.Feature.Test do
|
|
|
143
170
|
end
|
|
144
171
|
|
|
145
172
|
if ent == nil do
|
|
146
|
-
respond(404, nil, S.jm(["statusText", "Not found"]))
|
|
173
|
+
respond(fctx, 404, nil, S.jm(["statusText", "Not found"]))
|
|
147
174
|
else
|
|
148
175
|
if S.ismap(ent) and reqdata != nil do
|
|
149
176
|
Enum.each(H.entries(reqdata), fn {k, v} -> S.setprop(ent, k, v) end)
|
|
150
177
|
end
|
|
151
178
|
|
|
152
179
|
S.delprop(ent, "$KEY")
|
|
153
|
-
respond(200, S.clone(ent))
|
|
180
|
+
respond(fctx, 200, S.clone(ent))
|
|
154
181
|
end
|
|
155
182
|
|
|
156
183
|
"remove" ->
|
|
@@ -163,7 +190,7 @@ defmodule ProjectName.Feature.Test do
|
|
|
163
190
|
S.delprop(entmap, eid)
|
|
164
191
|
end
|
|
165
192
|
|
|
166
|
-
respond(200, nil)
|
|
193
|
+
respond(fctx, 200, nil)
|
|
167
194
|
|
|
168
195
|
"create" ->
|
|
169
196
|
build_args(f, fctx, op, S.getprop(fctx, "reqdata"))
|
|
@@ -175,13 +202,13 @@ defmodule ProjectName.Feature.Test do
|
|
|
175
202
|
S.setprop(ent, "id", eid)
|
|
176
203
|
if is_binary(eid), do: S.setprop(entmap, eid, ent)
|
|
177
204
|
S.delprop(ent, "$KEY")
|
|
178
|
-
respond(200, S.clone(ent))
|
|
205
|
+
respond(fctx, 200, S.clone(ent))
|
|
179
206
|
else
|
|
180
|
-
respond(200, ent)
|
|
207
|
+
respond(fctx, 200, ent)
|
|
181
208
|
end
|
|
182
209
|
|
|
183
210
|
_ ->
|
|
184
|
-
respond(404, nil, S.jm(["statusText", "Unknown operation"]))
|
|
211
|
+
respond(fctx, 404, nil, S.jm(["statusText", "Unknown operation"]))
|
|
185
212
|
end
|
|
186
213
|
end
|
|
187
214
|
|
|
@@ -258,7 +285,7 @@ defmodule ProjectName.Feature.Test do
|
|
|
258
285
|
netsleep.(pick_latency(net))
|
|
259
286
|
status = H.or_(S.getprop(net, "failStatus"), 503)
|
|
260
287
|
|
|
261
|
-
respond(status, nil,
|
|
288
|
+
respond(fctx, status, nil,
|
|
262
289
|
S.jm(["statusText", "Simulated Failure", "headers", S.jm([])]))
|
|
263
290
|
|
|
264
291
|
true ->
|