@reventlessdev/reventless-local 3.0.0-alpha.174 → 3.0.0-alpha.175

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.
@@ -0,0 +1,200 @@
1
+ // LocalEvents_Server protocol tests — socket-free.
2
+ //
3
+ // A `connection` is just a capturing `send` callback + subscription dict, so
4
+ // the AppSync Events frame handling, wildcard matching, LocalBus bridge and
5
+ // the publish route are all driven without a WebSocket. The ws attach glue is
6
+ // the only part not covered here (framework territory + a real socket).
7
+
8
+ open JestGlobals
9
+
10
+ let _ = TestRunner.setup()
11
+
12
+ let sentFrames: ref<array<string>> = ref([])
13
+
14
+ let makeConn = () => {
15
+ sentFrames := []
16
+ LocalEvents_Server.addConnection(~send=f => sentFrames.contents->Array.push(f))
17
+ }
18
+
19
+ let parseFrame = (s: string): dict<JSON.t> =>
20
+ s->JSON.parseOrThrow->JSON.Decode.object->Option.getOr(Dict.make())
21
+
22
+ let frameField = (s: string, field: string): option<string> =>
23
+ parseFrame(s)->Dict.get(field)->Option.flatMap(JSON.Decode.string)
24
+
25
+ let descriptor = (~id: string): JSON.t =>
26
+ Dict.fromArray([
27
+ ("changeKind", JSON.Encode.string("Updated")),
28
+ ("id", JSON.Encode.string(id)),
29
+ ("sortKeyValue", JSON.Encode.string("2026-07-28T00:00:00Z")),
30
+ ])->JSON.Encode.object
31
+
32
+ describe("LocalEvents_Server", () => {
33
+ beforeEach(() => LocalEvents_Server.resetConnections())
34
+
35
+ describe("channelMatches", () => {
36
+ testSync("exact match", () => {
37
+ expect(
38
+ LocalEvents_Server.channelMatches(
39
+ ~subscription="/default/Product/p-1",
40
+ ~channel="/default/Product/p-1",
41
+ ),
42
+ )->toBe(true)
43
+ })
44
+ testSync("wildcard prefix matches any depth", () => {
45
+ expect(
46
+ LocalEvents_Server.channelMatches(
47
+ ~subscription="/default/Product/*",
48
+ ~channel="/default/Product/p-1",
49
+ ),
50
+ )->toBe(true)
51
+ expect(
52
+ LocalEvents_Server.channelMatches(
53
+ ~subscription="/default/*",
54
+ ~channel="/default/Product/p-1/deep",
55
+ ),
56
+ )->toBe(true)
57
+ })
58
+ testSync("wildcard does not match a sibling with the same stem", () => {
59
+ expect(
60
+ LocalEvents_Server.channelMatches(
61
+ ~subscription="/default/Product/*",
62
+ ~channel="/default/ProductArchive/p-1",
63
+ ),
64
+ )->toBe(false)
65
+ })
66
+ testSync("non-wildcard mismatch", () => {
67
+ expect(
68
+ LocalEvents_Server.channelMatches(
69
+ ~subscription="/default/Product/p-1",
70
+ ~channel="/default/Product/p-2",
71
+ ),
72
+ )->toBe(false)
73
+ })
74
+ })
75
+
76
+ describe("subscribe protocol", () => {
77
+ testSync("connection_init is answered with connection_ack", () => {
78
+ let conn = makeConn()
79
+ LocalEvents_Server.handleFrame(conn, `{"type":"connection_init"}`)
80
+ expect(sentFrames.contents->Array.length)->toBe(1)
81
+ expect(sentFrames.contents->Array.getUnsafe(0)->frameField("type"))->toEqual(
82
+ Some("connection_ack"),
83
+ )
84
+ })
85
+
86
+ testSync("subscribe → state change → data frame on the subscription id", () => {
87
+ let conn = makeConn()
88
+ LocalEvents_Server.handleFrame(
89
+ conn,
90
+ `{"type":"subscribe","id":"sub-1","channel":"/default/Product/*"}`,
91
+ )
92
+ expect(sentFrames.contents->Array.getUnsafe(0)->frameField("type"))->toEqual(
93
+ Some("subscribe_success"),
94
+ )
95
+ LocalEvents_Server.broadcastStateChange(~name="Product", ~descriptor=descriptor(~id="p-1"))
96
+ expect(sentFrames.contents->Array.length)->toBe(2)
97
+ let data = sentFrames.contents->Array.getUnsafe(1)
98
+ expect(data->frameField("type"))->toEqual(Some("data"))
99
+ expect(data->frameField("id"))->toEqual(Some("sub-1"))
100
+ // `event` is a stringified JSON payload — parse the string to verify.
101
+ let event =
102
+ data->frameField("event")->Option.getOr("")->JSON.parseOrThrow->JSON.Decode.object
103
+ expect(
104
+ event->Option.flatMap(o => o->Dict.get("id"))->Option.flatMap(JSON.Decode.string),
105
+ )->toEqual(Some("p-1"))
106
+ })
107
+
108
+ testSync("channel segments are normalized like the AWS publisher", () => {
109
+ let conn = makeConn()
110
+ LocalEvents_Server.handleFrame(
111
+ conn,
112
+ `{"type":"subscribe","id":"s","channel":"/default/My-Model/order-1-2026"}`,
113
+ )
114
+ // Read-model name `My.Model` and entity key `order#1@2026` normalize to
115
+ // the subscribed channel (`[^A-Za-z0-9-]` → `-`).
116
+ LocalEvents_Server.broadcastStateChange(
117
+ ~name="My.Model",
118
+ ~descriptor=descriptor(~id="order#1@2026"),
119
+ )
120
+ expect(sentFrames.contents->Array.length)->toBe(2)
121
+ })
122
+
123
+ testSync("unsubscribe stops delivery", () => {
124
+ let conn = makeConn()
125
+ LocalEvents_Server.handleFrame(
126
+ conn,
127
+ `{"type":"subscribe","id":"sub-1","channel":"/default/Product/*"}`,
128
+ )
129
+ LocalEvents_Server.handleFrame(conn, `{"type":"unsubscribe","id":"sub-1"}`)
130
+ LocalEvents_Server.broadcastStateChange(~name="Product", ~descriptor=descriptor(~id="p-1"))
131
+ // subscribe_success + unsubscribe_success, but no data frame
132
+ expect(sentFrames.contents->Array.length)->toBe(2)
133
+ expect(sentFrames.contents->Array.getUnsafe(1)->frameField("type"))->toEqual(
134
+ Some("unsubscribe_success"),
135
+ )
136
+ })
137
+
138
+ testSync("malformed and unknown frames are ignored", () => {
139
+ let conn = makeConn()
140
+ LocalEvents_Server.handleFrame(conn, `not json`)
141
+ LocalEvents_Server.handleFrame(conn, `{"type":"mystery"}`)
142
+ LocalEvents_Server.handleFrame(conn, `{"type":"subscribe","id":"only-id"}`)
143
+ expect(sentFrames.contents->Array.length)->toBe(0)
144
+ })
145
+ })
146
+
147
+ describe("handlePublish", () => {
148
+ testSync("rejects non-client channels with 403", () => {
149
+ let (status, _) = LocalEvents_Server.handlePublish(
150
+ ~authorization=None,
151
+ ~body=`{"channel":"/default/Product/p-1","events":["{}"]}`,
152
+ )
153
+ expect(status)->toBe(403)
154
+ })
155
+
156
+ testSync("rejects an unverifiable token with 401", () => {
157
+ let (status, _) = LocalEvents_Server.handlePublish(
158
+ ~authorization=Some("garbage-token"),
159
+ ~body=`{"channel":"/client/x","events":["{}"]}`,
160
+ )
161
+ expect(status)->toBe(401)
162
+ })
163
+
164
+ testSync("rejects malformed bodies with 400", () => {
165
+ let (s1, _) = LocalEvents_Server.handlePublish(~authorization=None, ~body=`nope`)
166
+ let (s2, _) = LocalEvents_Server.handlePublish(
167
+ ~authorization=None,
168
+ ~body=`{"channel":"/client/x","events":[]}`,
169
+ )
170
+ expect(s1)->toBe(400)
171
+ expect(s2)->toBe(400)
172
+ })
173
+
174
+ testSync("fans out to a wildcard subscriber and accounts per event", () => {
175
+ let conn = makeConn()
176
+ LocalEvents_Server.handleFrame(
177
+ conn,
178
+ `{"type":"subscribe","id":"sub-p","channel":"/client/shop/presence/*"}`,
179
+ )
180
+ let (status, response) = LocalEvents_Server.handlePublish(
181
+ ~authorization=None,
182
+ ~body=`{"channel":"/client/shop/presence/room-1","events":["{\\"userId\\":\\"u1\\"}","not json",42]}`,
183
+ )
184
+ expect(status)->toBe(200)
185
+ let counts =
186
+ response
187
+ ->JSON.Decode.object
188
+ ->Option.map(o => (
189
+ o->Dict.get("successful")->Option.flatMap(JSON.Decode.array)->Option.getOr([])->Array.length,
190
+ o->Dict.get("failed")->Option.flatMap(JSON.Decode.array)->Option.getOr([])->Array.length,
191
+ ))
192
+ expect(counts)->toEqual(Some((1, 2)))
193
+ // subscribe_success + one data frame for the one valid event
194
+ expect(sentFrames.contents->Array.length)->toBe(2)
195
+ let data = sentFrames.contents->Array.getUnsafe(1)
196
+ expect(data->frameField("id"))->toEqual(Some("sub-p"))
197
+ expect(data->frameField("event"))->toEqual(Some(`{"userId":"u1"}`))
198
+ })
199
+ })
200
+ })
@@ -0,0 +1,147 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
4
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
+ import * as TestRunner$ReventlessLocal from "../../src/test/TestRunner.res.mjs";
6
+ import * as LocalEvents_Server$ReventlessLocal from "../../src/adapter/Api/LocalEvents_Server.res.mjs";
7
+
8
+ TestRunner$ReventlessLocal.setup();
9
+
10
+ let sentFrames = {
11
+ contents: []
12
+ };
13
+
14
+ function makeConn() {
15
+ sentFrames.contents = [];
16
+ return LocalEvents_Server$ReventlessLocal.addConnection(f => {
17
+ sentFrames.contents.push(f);
18
+ });
19
+ }
20
+
21
+ function parseFrame(s) {
22
+ return Stdlib_Option.getOr(Stdlib_JSON.Decode.object(JSON.parse(s)), {});
23
+ }
24
+
25
+ function frameField(s, field) {
26
+ return Stdlib_Option.flatMap(parseFrame(s)[field], Stdlib_JSON.Decode.string);
27
+ }
28
+
29
+ function descriptor(id) {
30
+ return Object.fromEntries([
31
+ [
32
+ "changeKind",
33
+ "Updated"
34
+ ],
35
+ [
36
+ "id",
37
+ id
38
+ ],
39
+ [
40
+ "sortKeyValue",
41
+ "2026-07-28T00:00:00Z"
42
+ ]
43
+ ]);
44
+ }
45
+
46
+ globalThis.describe("LocalEvents_Server", () => {
47
+ globalThis.beforeEach(() => LocalEvents_Server$ReventlessLocal.resetConnections());
48
+ globalThis.describe("channelMatches", () => {
49
+ globalThis.test("exact match", () => {
50
+ globalThis.expect(LocalEvents_Server$ReventlessLocal.channelMatches("/default/Product/p-1", "/default/Product/p-1")).toBe(true);
51
+ });
52
+ globalThis.test("wildcard prefix matches any depth", () => {
53
+ globalThis.expect(LocalEvents_Server$ReventlessLocal.channelMatches("/default/Product/*", "/default/Product/p-1")).toBe(true);
54
+ globalThis.expect(LocalEvents_Server$ReventlessLocal.channelMatches("/default/*", "/default/Product/p-1/deep")).toBe(true);
55
+ });
56
+ globalThis.test("wildcard does not match a sibling with the same stem", () => {
57
+ globalThis.expect(LocalEvents_Server$ReventlessLocal.channelMatches("/default/Product/*", "/default/ProductArchive/p-1")).toBe(false);
58
+ });
59
+ globalThis.test("non-wildcard mismatch", () => {
60
+ globalThis.expect(LocalEvents_Server$ReventlessLocal.channelMatches("/default/Product/p-1", "/default/Product/p-2")).toBe(false);
61
+ });
62
+ });
63
+ globalThis.describe("subscribe protocol", () => {
64
+ globalThis.test("connection_init is answered with connection_ack", () => {
65
+ let conn = makeConn();
66
+ LocalEvents_Server$ReventlessLocal.handleFrame(conn, `{"type":"connection_init"}`);
67
+ globalThis.expect(sentFrames.contents.length).toBe(1);
68
+ globalThis.expect(frameField(sentFrames.contents[0], "type")).toEqual("connection_ack");
69
+ });
70
+ globalThis.test("subscribe → state change → data frame on the subscription id", () => {
71
+ let conn = makeConn();
72
+ LocalEvents_Server$ReventlessLocal.handleFrame(conn, `{"type":"subscribe","id":"sub-1","channel":"/default/Product/*"}`);
73
+ globalThis.expect(frameField(sentFrames.contents[0], "type")).toEqual("subscribe_success");
74
+ LocalEvents_Server$ReventlessLocal.broadcastStateChange("Product", descriptor("p-1"));
75
+ globalThis.expect(sentFrames.contents.length).toBe(2);
76
+ let data = sentFrames.contents[1];
77
+ globalThis.expect(frameField(data, "type")).toEqual("data");
78
+ globalThis.expect(frameField(data, "id")).toEqual("sub-1");
79
+ let event = Stdlib_JSON.Decode.object(JSON.parse(Stdlib_Option.getOr(frameField(data, "event"), "")));
80
+ globalThis.expect(Stdlib_Option.flatMap(Stdlib_Option.flatMap(event, o => o["id"]), Stdlib_JSON.Decode.string)).toEqual("p-1");
81
+ });
82
+ globalThis.test("channel segments are normalized like the AWS publisher", () => {
83
+ let conn = makeConn();
84
+ LocalEvents_Server$ReventlessLocal.handleFrame(conn, `{"type":"subscribe","id":"s","channel":"/default/My-Model/order-1-2026"}`);
85
+ LocalEvents_Server$ReventlessLocal.broadcastStateChange("My.Model", descriptor("order#1@2026"));
86
+ globalThis.expect(sentFrames.contents.length).toBe(2);
87
+ });
88
+ globalThis.test("unsubscribe stops delivery", () => {
89
+ let conn = makeConn();
90
+ LocalEvents_Server$ReventlessLocal.handleFrame(conn, `{"type":"subscribe","id":"sub-1","channel":"/default/Product/*"}`);
91
+ LocalEvents_Server$ReventlessLocal.handleFrame(conn, `{"type":"unsubscribe","id":"sub-1"}`);
92
+ LocalEvents_Server$ReventlessLocal.broadcastStateChange("Product", descriptor("p-1"));
93
+ globalThis.expect(sentFrames.contents.length).toBe(2);
94
+ globalThis.expect(frameField(sentFrames.contents[1], "type")).toEqual("unsubscribe_success");
95
+ });
96
+ globalThis.test("malformed and unknown frames are ignored", () => {
97
+ let conn = makeConn();
98
+ LocalEvents_Server$ReventlessLocal.handleFrame(conn, `not json`);
99
+ LocalEvents_Server$ReventlessLocal.handleFrame(conn, `{"type":"mystery"}`);
100
+ LocalEvents_Server$ReventlessLocal.handleFrame(conn, `{"type":"subscribe","id":"only-id"}`);
101
+ globalThis.expect(sentFrames.contents.length).toBe(0);
102
+ });
103
+ });
104
+ globalThis.describe("handlePublish", () => {
105
+ globalThis.test("rejects non-client channels with 403", () => {
106
+ let match = LocalEvents_Server$ReventlessLocal.handlePublish(undefined, `{"channel":"/default/Product/p-1","events":["{}"]}`);
107
+ globalThis.expect(match[0]).toBe(403);
108
+ });
109
+ globalThis.test("rejects an unverifiable token with 401", () => {
110
+ let match = LocalEvents_Server$ReventlessLocal.handlePublish("garbage-token", `{"channel":"/client/x","events":["{}"]}`);
111
+ globalThis.expect(match[0]).toBe(401);
112
+ });
113
+ globalThis.test("rejects malformed bodies with 400", () => {
114
+ let match = LocalEvents_Server$ReventlessLocal.handlePublish(undefined, `nope`);
115
+ let match$1 = LocalEvents_Server$ReventlessLocal.handlePublish(undefined, `{"channel":"/client/x","events":[]}`);
116
+ globalThis.expect(match[0]).toBe(400);
117
+ globalThis.expect(match$1[0]).toBe(400);
118
+ });
119
+ globalThis.test("fans out to a wildcard subscriber and accounts per event", () => {
120
+ let conn = makeConn();
121
+ LocalEvents_Server$ReventlessLocal.handleFrame(conn, `{"type":"subscribe","id":"sub-p","channel":"/client/shop/presence/*"}`);
122
+ let match = LocalEvents_Server$ReventlessLocal.handlePublish(undefined, `{"channel":"/client/shop/presence/room-1","events":["{\\"userId\\":\\"u1\\"}","not json",42]}`);
123
+ globalThis.expect(match[0]).toBe(200);
124
+ let counts = Stdlib_Option.map(Stdlib_JSON.Decode.object(match[1]), o => [
125
+ Stdlib_Option.getOr(Stdlib_Option.flatMap(o["successful"], Stdlib_JSON.Decode.array), []).length,
126
+ Stdlib_Option.getOr(Stdlib_Option.flatMap(o["failed"], Stdlib_JSON.Decode.array), []).length
127
+ ]);
128
+ globalThis.expect(counts).toEqual([
129
+ 1,
130
+ 2
131
+ ]);
132
+ globalThis.expect(sentFrames.contents.length).toBe(2);
133
+ let data = sentFrames.contents[1];
134
+ globalThis.expect(frameField(data, "id")).toEqual("sub-p");
135
+ globalThis.expect(frameField(data, "event")).toEqual(`{"userId":"u1"}`);
136
+ });
137
+ });
138
+ });
139
+
140
+ export {
141
+ sentFrames,
142
+ makeConn,
143
+ parseFrame,
144
+ frameField,
145
+ descriptor,
146
+ }
147
+ /* Not a pure module */