serverless-ircd 0.7.0 → 0.9.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/.github/workflows/ci.yml +7 -3
- package/.gitmodules +1 -1
- package/CHANGELOG.md +518 -29
- package/README.md +221 -161
- package/apps/aws-stack/package.json +1 -1
- package/apps/aws-stack/src/aws-stack.ts +186 -18
- package/apps/aws-stack/tests/stack.test.ts +400 -56
- package/apps/cf-tcp-container/package.json +1 -1
- package/apps/cf-tcp-container/src/container-server.ts +21 -1
- package/apps/cf-tcp-container/tests/config-loader.test.ts +43 -0
- package/apps/cf-tcp-container/tests/container-server.test.ts +249 -1
- package/apps/cf-tcp-container/tests/persistence.test.ts +9 -0
- package/apps/cf-tcp-container/tests/tls-e2e.test.ts +24 -5
- package/apps/cf-worker/package.json +1 -1
- package/apps/cf-worker/src/worker.ts +4 -4
- package/apps/cf-worker/tests/fixtures/web-dist/webclient/index.html +18 -0
- package/apps/cf-worker/tests/smoke.test.ts +5 -5
- package/apps/cf-worker/wrangler.test.toml +6 -6
- package/apps/cf-worker/wrangler.toml +7 -5
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/config-loader.ts +8 -0
- package/apps/local-cli/src/main.ts +16 -0
- package/apps/local-cli/src/server.ts +95 -31
- package/apps/local-cli/tests/config-loader.test.ts +14 -0
- package/apps/local-cli/tests/config-resolution.test.ts +65 -0
- package/apps/local-cli/tests/motd-file-non-error.test.ts +29 -0
- package/apps/local-cli/tests/rehash.test.ts +147 -0
- package/apps/local-cli/tests/server-helpers.test.ts +63 -0
- package/apps/local-cli/tests/tcp.test.ts +89 -0
- package/apps/local-cli/tests/ws-subprotocol.test.ts +92 -0
- package/apps/web/landing/index.html +237 -16
- package/apps/web/package.json +3 -2
- package/apps/web/scripts/build.mjs +46 -16
- package/apps/web/src/build-env.ts +1 -1
- package/apps/web/src/config-schema.ts +6 -6
- package/apps/web/src/render-docs.ts +292 -0
- package/apps/web/tests/build-smoke.test.ts +43 -14
- package/apps/web/tests/config-schema.test.ts +1 -1
- package/apps/web/tests/landing-content.test.ts +103 -0
- package/apps/web/tests/render-docs.test.ts +198 -0
- package/docs/AWS-Adapter-Architecture.md +3 -2
- package/docs/AWS-Deployment.md +21 -8
- package/docs/Cloudflare-Deployment-Guide.md +22 -6
- package/docs/PlanExtensions.md +113 -3
- package/docs/Release-Process.md +23 -13
- package/docs/Services.md +578 -0
- package/docs/WebClientGuide.md +32 -31
- package/package.json +2 -2
- package/packages/aws-adapter/package.json +1 -1
- package/packages/aws-adapter/src/aws-runtime.ts +20 -1
- package/packages/aws-adapter/src/cdk-table-defs.ts +6 -0
- package/packages/aws-adapter/src/config-loader.ts +11 -0
- package/packages/aws-adapter/src/connection-counter.ts +89 -0
- package/packages/aws-adapter/src/dynamo-services-store.ts +649 -0
- package/packages/aws-adapter/src/handlers/connect.ts +55 -51
- package/packages/aws-adapter/src/handlers/default.ts +36 -4
- package/packages/aws-adapter/src/handlers/index.ts +15 -0
- package/packages/aws-adapter/src/handlers/nlb-stream.ts +25 -2
- package/packages/aws-adapter/src/handlers/sweeper.ts +5 -1
- package/packages/aws-adapter/src/index.ts +4 -0
- package/packages/aws-adapter/src/stats.ts +6 -1
- package/packages/aws-adapter/src/tables.ts +34 -4
- package/packages/aws-adapter/tests/aws-harness.ts +3 -0
- package/packages/aws-adapter/tests/aws-runtime.test.ts +23 -1
- package/packages/aws-adapter/tests/config-loader.test.ts +8 -0
- package/packages/aws-adapter/tests/connect.test.ts +158 -32
- package/packages/aws-adapter/tests/connection-counter.test.ts +144 -0
- package/packages/aws-adapter/tests/dynamo-services-store-dynamo.test.ts +183 -0
- package/packages/aws-adapter/tests/dynamo-services-store-unit.test.ts +568 -0
- package/packages/aws-adapter/tests/global-setup.ts +28 -1
- package/packages/aws-adapter/tests/gone-exception.test.ts +21 -2
- package/packages/aws-adapter/tests/handlers.test.ts +105 -3
- package/packages/aws-adapter/tests/nlb-stream.test.ts +29 -1
- package/packages/aws-adapter/tests/sweeper.test.ts +20 -0
- package/packages/aws-adapter/tests/tables.test.ts +6 -1
- package/packages/cf-adapter/package.json +1 -1
- package/packages/cf-adapter/src/config-loader.ts +11 -0
- package/packages/cf-adapter/src/connection-do.ts +130 -8
- package/packages/cf-adapter/src/d1-services-store.ts +703 -0
- package/packages/cf-adapter/src/env.ts +8 -0
- package/packages/cf-adapter/src/index.ts +5 -0
- package/packages/cf-adapter/tests/config-loader.test.ts +19 -0
- package/packages/cf-adapter/tests/connection-do-nickserv-d1.test.ts +128 -0
- package/packages/cf-adapter/tests/connection-do-pure.test.ts +130 -0
- package/packages/cf-adapter/tests/connection-do.test.ts +150 -2
- package/packages/cf-adapter/tests/d1-services-store.test.ts +582 -0
- package/packages/cf-adapter/tests/serialize.test.ts +1 -0
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/irc-core/package.json +1 -1
- package/packages/irc-core/scripts/generate-build-info.mjs +26 -5
- package/packages/irc-core/src/commands/account-auth.ts +200 -0
- package/packages/irc-core/src/commands/chanserv.ts +1166 -0
- package/packages/irc-core/src/commands/hostserv.ts +522 -0
- package/packages/irc-core/src/commands/index.ts +13 -0
- package/packages/irc-core/src/commands/join.ts +171 -9
- package/packages/irc-core/src/commands/markread.ts +202 -0
- package/packages/irc-core/src/commands/memoserv.ts +319 -0
- package/packages/irc-core/src/commands/mode.ts +96 -4
- package/packages/irc-core/src/commands/nickserv.ts +402 -0
- package/packages/irc-core/src/commands/oper.ts +18 -1
- package/packages/irc-core/src/commands/operserv.ts +346 -0
- package/packages/irc-core/src/commands/pre-away.ts +3 -1
- package/packages/irc-core/src/commands/privmsg.ts +42 -0
- package/packages/irc-core/src/commands/read-marker.ts +8 -8
- package/packages/irc-core/src/commands/registration.ts +78 -12
- package/packages/irc-core/src/commands/sasl.ts +18 -49
- package/packages/irc-core/src/commands/service-aliases.ts +52 -0
- package/packages/irc-core/src/commands/tagmsg.ts +41 -6
- package/packages/irc-core/src/commands/topic.ts +60 -10
- package/packages/irc-core/src/config.ts +36 -5
- package/packages/irc-core/src/effects.ts +56 -1
- package/packages/irc-core/src/ports.ts +1653 -84
- package/packages/irc-core/src/protocol/numerics.ts +8 -0
- package/packages/irc-core/src/state/channel.ts +38 -1
- package/packages/irc-core/src/state/connection.ts +25 -1
- package/packages/irc-core/src/types.ts +48 -12
- package/packages/irc-core/tests/commands/chanserv.test.ts +2335 -0
- package/packages/irc-core/tests/commands/chathistory.test.ts +6 -0
- package/packages/irc-core/tests/commands/hostserv.test.ts +1006 -0
- package/packages/irc-core/tests/commands/join.test.ts +572 -1
- package/packages/irc-core/tests/commands/markread.test.ts +361 -0
- package/packages/irc-core/tests/commands/memoserv.test.ts +654 -0
- package/packages/irc-core/tests/commands/mode.test.ts +381 -2
- package/packages/irc-core/tests/commands/nickserv.test.ts +990 -0
- package/packages/irc-core/tests/commands/oper.test.ts +13 -0
- package/packages/irc-core/tests/commands/operserv.test.ts +656 -0
- package/packages/irc-core/tests/commands/privmsg.test.ts +147 -0
- package/packages/irc-core/tests/commands/read-marker.test.ts +28 -28
- package/packages/irc-core/tests/commands/registration.test.ts +1015 -20
- package/packages/irc-core/tests/commands/sasl.test.ts +229 -12
- package/packages/irc-core/tests/commands/server-info.test.ts +9 -5
- package/packages/irc-core/tests/commands/service-aliases.test.ts +52 -0
- package/packages/irc-core/tests/commands/tagmsg.test.ts +73 -33
- package/packages/irc-core/tests/commands/topic.test.ts +94 -2
- package/packages/irc-core/tests/commands/unified-account.test.ts +416 -0
- package/packages/irc-core/tests/config.test.ts +49 -5
- package/packages/irc-core/tests/effects.test.ts +19 -0
- package/packages/irc-core/tests/message-store.test.ts +63 -0
- package/packages/irc-core/tests/persistent-services-store.test.ts +582 -0
- package/packages/irc-core/tests/services-store.test.ts +1289 -0
- package/packages/irc-core/tests/state/channel.test.ts +3 -0
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +121 -16
- package/packages/irc-server/src/dispatch.ts +94 -7
- package/packages/irc-server/src/routing.ts +19 -0
- package/packages/irc-server/tests/actor.test.ts +988 -15
- package/packages/irc-server/tests/dispatch.test.ts +270 -2
- package/packages/irc-server/tests/routing.test.ts +6 -0
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/in-memory-harness.ts +37 -8
- package/packages/irc-test-support/src/index.ts +1 -0
- package/packages/irc-test-support/src/scenarios.ts +21 -6
- package/packages/irc-test-support/tests/in-memory-harness.test.ts +51 -0
- package/packages/irc-test-support/vitest.config.ts +6 -1
- package/tools/ci-hardening/package.json +1 -1
- package/tools/load-test/package.json +1 -1
- package/tools/load-test/src/client.ts +13 -13
- package/tools/load-test/tests/client.test.ts +258 -2
- package/tools/load-test/tests/config.test.ts +39 -0
- package/tools/load-test/tests/harness.test.ts +21 -0
- package/tools/load-test/tests/metrics.test.ts +7 -0
- package/tools/tcp-ws-forwarder/package.json +1 -1
- package/tools/tcp-ws-forwarder/tests/close-error.test.ts +40 -0
- package/tools/tcp-ws-forwarder/tests/defensive-branches.test.ts +78 -0
- package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +53 -2
- package/tools/tcp-ws-forwarder/tests/logger.test.ts +31 -1
- package/apps/cf-worker/tests/fixtures/web-dist/app/index.html +0 -18
- package/packages/irc-core/tests/read-marker-store.test.ts +0 -108
|
@@ -958,3 +958,150 @@ describe('privmsgChannelReducer — chathistory recording', () => {
|
|
|
958
958
|
expect(recorded.map((m) => m.msgid)).toEqual(['nonce-0', 'nonce-1']);
|
|
959
959
|
});
|
|
960
960
|
});
|
|
961
|
+
|
|
962
|
+
// ============================================================================
|
|
963
|
+
// channelReducer — ChanServ +R (block unidentified PRIVMSG)
|
|
964
|
+
// ============================================================================
|
|
965
|
+
|
|
966
|
+
describe('privmsgChannelReducer — +R (block unidentified PRIVMSG)', () => {
|
|
967
|
+
it('rejects an unidentified member with 404 ERR_CANNOTSENDTOCHAN', () => {
|
|
968
|
+
const chan = makeChan('#foo');
|
|
969
|
+
chan.modes.blockUnidentified = true;
|
|
970
|
+
addMember(chan, 'c1', 'guest');
|
|
971
|
+
const conn = makeConn('c1', 'guest');
|
|
972
|
+
// guest has no account.
|
|
973
|
+
const ctx = makeCtx(conn);
|
|
974
|
+
|
|
975
|
+
const out = privmsgChannelReducer(
|
|
976
|
+
chan,
|
|
977
|
+
{ command: 'PRIVMSG', params: ['#foo', 'hello'], tags: {} },
|
|
978
|
+
ctx,
|
|
979
|
+
);
|
|
980
|
+
|
|
981
|
+
expect(out.effects.filter((e) => e.tag === 'Broadcast')).toHaveLength(0);
|
|
982
|
+
const texts = out.effects.flatMap((e) => (e.tag === 'Send' ? e.lines.map((l) => l.text) : []));
|
|
983
|
+
expect(texts).toContain(':irc.example.com 404 guest #foo :Cannot send to channel');
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
it('admits an identified member (account set)', () => {
|
|
987
|
+
const chan = makeChan('#foo');
|
|
988
|
+
chan.modes.blockUnidentified = true;
|
|
989
|
+
addMember(chan, 'c1', 'alice');
|
|
990
|
+
const conn = makeConn('c1', 'alice');
|
|
991
|
+
conn.account = 'alice';
|
|
992
|
+
const ctx = makeCtx(conn);
|
|
993
|
+
|
|
994
|
+
const out = privmsgChannelReducer(
|
|
995
|
+
chan,
|
|
996
|
+
{ command: 'PRIVMSG', params: ['#foo', 'hello'], tags: {} },
|
|
997
|
+
ctx,
|
|
998
|
+
);
|
|
999
|
+
|
|
1000
|
+
expect(out.effects.filter((e) => e.tag === 'Broadcast')).toHaveLength(1);
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
it('NOTICE is silent on +R rejection (RFC: NOTICE never produces numerics)', () => {
|
|
1004
|
+
const chan = makeChan('#foo');
|
|
1005
|
+
chan.modes.blockUnidentified = true;
|
|
1006
|
+
addMember(chan, 'c1', 'guest');
|
|
1007
|
+
const conn = makeConn('c1', 'guest');
|
|
1008
|
+
const ctx = makeCtx(conn);
|
|
1009
|
+
|
|
1010
|
+
const out = noticeChannelReducer(
|
|
1011
|
+
chan,
|
|
1012
|
+
{ command: 'NOTICE', params: ['#foo', 'hello'], tags: {} },
|
|
1013
|
+
ctx,
|
|
1014
|
+
);
|
|
1015
|
+
|
|
1016
|
+
expect(out.effects).toEqual<EffectType[]>([]);
|
|
1017
|
+
});
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
// ============================================================================
|
|
1021
|
+
// channelReducer — ChanServ +M (moderated requires identification)
|
|
1022
|
+
// ============================================================================
|
|
1023
|
+
|
|
1024
|
+
describe('privmsgChannelReducer — +M (moderated-ident)', () => {
|
|
1025
|
+
it('rejects an unidentified non-op/non-voice member with 404', () => {
|
|
1026
|
+
const chan = makeChan('#foo');
|
|
1027
|
+
chan.modes.moderatedIdent = true;
|
|
1028
|
+
addMember(chan, 'c1', 'guest');
|
|
1029
|
+
const conn = makeConn('c1', 'guest');
|
|
1030
|
+
const ctx = makeCtx(conn);
|
|
1031
|
+
|
|
1032
|
+
const out = privmsgChannelReducer(
|
|
1033
|
+
chan,
|
|
1034
|
+
{ command: 'PRIVMSG', params: ['#foo', 'hi'], tags: {} },
|
|
1035
|
+
ctx,
|
|
1036
|
+
);
|
|
1037
|
+
|
|
1038
|
+
expect(out.effects.filter((e) => e.tag === 'Broadcast')).toHaveLength(0);
|
|
1039
|
+
const texts = out.effects.flatMap((e) => (e.tag === 'Send' ? e.lines.map((l) => l.text) : []));
|
|
1040
|
+
expect(texts).toContain(':irc.example.com 404 guest #foo :Cannot send to channel');
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
it('admits an identified non-op/non-voice member', () => {
|
|
1044
|
+
const chan = makeChan('#foo');
|
|
1045
|
+
chan.modes.moderatedIdent = true;
|
|
1046
|
+
addMember(chan, 'c1', 'alice');
|
|
1047
|
+
const conn = makeConn('c1', 'alice');
|
|
1048
|
+
conn.account = 'alice';
|
|
1049
|
+
const ctx = makeCtx(conn);
|
|
1050
|
+
|
|
1051
|
+
const out = privmsgChannelReducer(
|
|
1052
|
+
chan,
|
|
1053
|
+
{ command: 'PRIVMSG', params: ['#foo', 'hi'], tags: {} },
|
|
1054
|
+
ctx,
|
|
1055
|
+
);
|
|
1056
|
+
|
|
1057
|
+
expect(out.effects.filter((e) => e.tag === 'Broadcast')).toHaveLength(1);
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
it('admits an unidentified op (op bypasses +M)', () => {
|
|
1061
|
+
const chan = makeChan('#foo');
|
|
1062
|
+
chan.modes.moderatedIdent = true;
|
|
1063
|
+
addMember(chan, 'c1', 'guest', true);
|
|
1064
|
+
const conn = makeConn('c1', 'guest');
|
|
1065
|
+
const ctx = makeCtx(conn);
|
|
1066
|
+
|
|
1067
|
+
const out = privmsgChannelReducer(
|
|
1068
|
+
chan,
|
|
1069
|
+
{ command: 'PRIVMSG', params: ['#foo', 'hi'], tags: {} },
|
|
1070
|
+
ctx,
|
|
1071
|
+
);
|
|
1072
|
+
|
|
1073
|
+
expect(out.effects.filter((e) => e.tag === 'Broadcast')).toHaveLength(1);
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
it('admits an unidentified voice (voice bypasses +M)', () => {
|
|
1077
|
+
const chan = makeChan('#foo');
|
|
1078
|
+
chan.modes.moderatedIdent = true;
|
|
1079
|
+
addMember(chan, 'c1', 'guest', false, true);
|
|
1080
|
+
const conn = makeConn('c1', 'guest');
|
|
1081
|
+
const ctx = makeCtx(conn);
|
|
1082
|
+
|
|
1083
|
+
const out = privmsgChannelReducer(
|
|
1084
|
+
chan,
|
|
1085
|
+
{ command: 'PRIVMSG', params: ['#foo', 'hi'], tags: {} },
|
|
1086
|
+
ctx,
|
|
1087
|
+
);
|
|
1088
|
+
|
|
1089
|
+
expect(out.effects.filter((e) => e.tag === 'Broadcast')).toHaveLength(1);
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
it('NOTICE is silent on +M rejection (RFC: NOTICE never produces numerics)', () => {
|
|
1093
|
+
const chan = makeChan('#foo');
|
|
1094
|
+
chan.modes.moderatedIdent = true;
|
|
1095
|
+
addMember(chan, 'c1', 'guest');
|
|
1096
|
+
const conn = makeConn('c1', 'guest');
|
|
1097
|
+
const ctx = makeCtx(conn);
|
|
1098
|
+
|
|
1099
|
+
const out = noticeChannelReducer(
|
|
1100
|
+
chan,
|
|
1101
|
+
{ command: 'NOTICE', params: ['#foo', 'hi'], tags: {} },
|
|
1102
|
+
ctx,
|
|
1103
|
+
);
|
|
1104
|
+
|
|
1105
|
+
expect(out.effects).toEqual<EffectType[]>([]);
|
|
1106
|
+
});
|
|
1107
|
+
});
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
EmptyMotdProvider,
|
|
12
12
|
FakeClock,
|
|
13
|
-
|
|
13
|
+
InMemoryServicesStore,
|
|
14
14
|
SequentialIdFactory,
|
|
15
15
|
} from '../../src/ports';
|
|
16
16
|
import { type ConnectionState, createConnection } from '../../src/state/connection';
|
|
@@ -38,14 +38,14 @@ function makeConn(id = 'c1', account?: string): ConnectionState {
|
|
|
38
38
|
return s;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function makeCtx(conn: ConnectionState,
|
|
41
|
+
function makeCtx(conn: ConnectionState, services?: InMemoryServicesStore): Ctx {
|
|
42
42
|
return buildCtx({
|
|
43
43
|
serverConfig,
|
|
44
44
|
clock: new FakeClock(1_000),
|
|
45
45
|
ids: new SequentialIdFactory(),
|
|
46
46
|
motd: EmptyMotdProvider,
|
|
47
47
|
connection: conn,
|
|
48
|
-
...(
|
|
48
|
+
...(services !== undefined ? { services } : {}),
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -109,18 +109,18 @@ describe('readMarkerMsgid', () => {
|
|
|
109
109
|
|
|
110
110
|
describe('persistReadMarker', () => {
|
|
111
111
|
it('writes the marker to the store keyed by (account, channel)', () => {
|
|
112
|
-
const
|
|
113
|
-
const ctx = makeCtx(makeConn('c1', 'alice'),
|
|
112
|
+
const services = new InMemoryServicesStore();
|
|
113
|
+
const ctx = makeCtx(makeConn('c1', 'alice'), services);
|
|
114
114
|
|
|
115
115
|
persistReadMarker(ctx, '#foo', 'm7');
|
|
116
116
|
|
|
117
|
-
expect(
|
|
117
|
+
expect(services.getLastReadMarker('alice', '#foo')).toBe('m7');
|
|
118
118
|
});
|
|
119
119
|
|
|
120
120
|
it('also advances the connection in-memory lastReadMarkers', () => {
|
|
121
|
-
const
|
|
121
|
+
const services = new InMemoryServicesStore();
|
|
122
122
|
const conn = makeConn('c1', 'alice');
|
|
123
|
-
const ctx = makeCtx(conn,
|
|
123
|
+
const ctx = makeCtx(conn, services);
|
|
124
124
|
|
|
125
125
|
persistReadMarker(ctx, '#foo', 'm7');
|
|
126
126
|
|
|
@@ -128,16 +128,16 @@ describe('persistReadMarker', () => {
|
|
|
128
128
|
});
|
|
129
129
|
|
|
130
130
|
it('overwrites a previously stored marker for the same pair', () => {
|
|
131
|
-
const
|
|
132
|
-
const ctx = makeCtx(makeConn('c1', 'alice'),
|
|
131
|
+
const services = new InMemoryServicesStore();
|
|
132
|
+
const ctx = makeCtx(makeConn('c1', 'alice'), services);
|
|
133
133
|
|
|
134
134
|
persistReadMarker(ctx, '#foo', 'm1');
|
|
135
135
|
persistReadMarker(ctx, '#foo', 'm2');
|
|
136
136
|
|
|
137
|
-
expect(
|
|
137
|
+
expect(services.getLastReadMarker('alice', '#foo')).toBe('m2');
|
|
138
138
|
});
|
|
139
139
|
|
|
140
|
-
it('is a no-op when no
|
|
140
|
+
it('is a no-op when no ServicesStore is bound', () => {
|
|
141
141
|
const conn = makeConn('c1', 'alice');
|
|
142
142
|
const ctx = makeCtx(conn); // no store
|
|
143
143
|
|
|
@@ -148,14 +148,14 @@ describe('persistReadMarker', () => {
|
|
|
148
148
|
});
|
|
149
149
|
|
|
150
150
|
it('is a no-op on the store when the connection has no account', () => {
|
|
151
|
-
const
|
|
151
|
+
const services = new InMemoryServicesStore();
|
|
152
152
|
const conn = makeConn('c1'); // no account
|
|
153
|
-
const ctx = makeCtx(conn,
|
|
153
|
+
const ctx = makeCtx(conn, services);
|
|
154
154
|
|
|
155
155
|
persistReadMarker(ctx, '#foo', 'm1');
|
|
156
156
|
|
|
157
|
-
expect(
|
|
158
|
-
expect(
|
|
157
|
+
expect(services.getLastReadMarker('alice', '#foo')).toBeUndefined();
|
|
158
|
+
expect(services.listReadMarkers('alice')).toEqual([]);
|
|
159
159
|
});
|
|
160
160
|
});
|
|
161
161
|
|
|
@@ -165,44 +165,44 @@ describe('persistReadMarker', () => {
|
|
|
165
165
|
|
|
166
166
|
describe('seedReadMarkers', () => {
|
|
167
167
|
it('populates lastReadMarkers from every persisted entry for the account', () => {
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
168
|
+
const services = new InMemoryServicesStore();
|
|
169
|
+
services.setLastReadMarker('alice', '#foo', 'm1');
|
|
170
|
+
services.setLastReadMarker('alice', '#bar', 'm2');
|
|
171
171
|
const conn = makeConn();
|
|
172
172
|
|
|
173
|
-
seedReadMarkers(conn,
|
|
173
|
+
seedReadMarkers(conn, services, 'alice');
|
|
174
174
|
|
|
175
175
|
expect(conn.lastReadMarkers?.get('#foo')).toBe('m1');
|
|
176
176
|
expect(conn.lastReadMarkers?.get('#bar')).toBe('m2');
|
|
177
177
|
});
|
|
178
178
|
|
|
179
179
|
it('leaves existing in-memory markers untouched when the store is empty', () => {
|
|
180
|
-
const
|
|
180
|
+
const services = new InMemoryServicesStore();
|
|
181
181
|
const conn = makeConn();
|
|
182
182
|
conn.lastReadMarkers = new Map<string, string>([['#foo', 'm9']]);
|
|
183
183
|
|
|
184
|
-
seedReadMarkers(conn,
|
|
184
|
+
seedReadMarkers(conn, services, 'alice');
|
|
185
185
|
|
|
186
186
|
expect(conn.lastReadMarkers?.get('#foo')).toBe('m9');
|
|
187
187
|
});
|
|
188
188
|
|
|
189
189
|
it('seeds markers for an account regardless of case spelling', () => {
|
|
190
|
-
const
|
|
191
|
-
|
|
190
|
+
const services = new InMemoryServicesStore();
|
|
191
|
+
services.setLastReadMarker('Alice', '#foo', 'm1');
|
|
192
192
|
const conn = makeConn();
|
|
193
193
|
|
|
194
|
-
seedReadMarkers(conn,
|
|
194
|
+
seedReadMarkers(conn, services, 'alice');
|
|
195
195
|
|
|
196
196
|
expect(conn.lastReadMarkers?.get('#foo')).toBe('m1');
|
|
197
197
|
});
|
|
198
198
|
|
|
199
199
|
it('does not overwrite an in-memory marker already present for a channel', () => {
|
|
200
|
-
const
|
|
201
|
-
|
|
200
|
+
const services = new InMemoryServicesStore();
|
|
201
|
+
services.setLastReadMarker('alice', '#foo', 'stored');
|
|
202
202
|
const conn = makeConn();
|
|
203
203
|
conn.lastReadMarkers = new Map<string, string>([['#foo', 'in-memory']]);
|
|
204
204
|
|
|
205
|
-
seedReadMarkers(conn,
|
|
205
|
+
seedReadMarkers(conn, services, 'alice');
|
|
206
206
|
|
|
207
207
|
// The in-memory (session) marker wins: the connection may have already
|
|
208
208
|
// advanced past the persisted value this session.
|