serverless-ircd 0.1.0 → 0.3.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 +96 -2
- package/.github/workflows/deploy-aws.yml +129 -0
- package/.github/workflows/deploy-cf.yml +0 -2
- package/.gitmodules +3 -0
- package/CHANGELOG.md +436 -0
- package/README.md +127 -84
- package/apps/aws-stack/README.md +73 -0
- package/apps/aws-stack/bin/aws.ts +49 -0
- package/apps/aws-stack/cdk.json +10 -0
- package/apps/aws-stack/package.json +41 -0
- package/apps/aws-stack/scripts/smoke-helpers.d.mts +22 -0
- package/apps/aws-stack/scripts/smoke-helpers.mjs +89 -0
- package/apps/aws-stack/scripts/smoke.mjs +142 -0
- package/apps/aws-stack/src/aws-stack.ts +263 -0
- package/apps/aws-stack/src/tables.ts +10 -0
- package/apps/aws-stack/tests/localstack.test.ts +46 -0
- package/apps/aws-stack/tests/smoke-helpers.test.ts +98 -0
- package/apps/aws-stack/tests/stack.test.ts +464 -0
- package/apps/aws-stack/tsconfig.build.json +11 -0
- package/apps/aws-stack/tsconfig.test.json +8 -0
- package/apps/aws-stack/vitest.config.ts +20 -0
- package/apps/cf-worker/package.json +1 -1
- package/apps/cf-worker/scripts/smoke.mjs +0 -0
- package/apps/cf-worker/src/worker.ts +8 -2
- package/apps/cf-worker/tests/smoke.test.ts +1 -0
- package/apps/cf-worker/wrangler.test.toml +5 -1
- package/apps/cf-worker/wrangler.toml +66 -17
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/config-loader.ts +57 -0
- package/apps/local-cli/src/main.ts +1 -1
- package/apps/local-cli/src/server.ts +267 -32
- package/apps/local-cli/tests/config-loader.test.ts +107 -0
- package/apps/local-cli/tests/e2e.test.ts +126 -0
- package/apps/local-cli/tests/security-e2e.test.ts +239 -0
- package/biome.json +3 -1
- package/docs/ADR-001-pure-reducers-and-effect-system.md +74 -0
- package/docs/ADR-002-location-of-authority.md +82 -0
- package/docs/ADR-003-durable-object-sharding.md +93 -0
- package/docs/ADR-004-dynamodb-schema.md +96 -0
- package/docs/ADR-005-wss-only-transport-v1.md +83 -0
- package/docs/ADR-006-sasl-mechanism-scope.md +86 -0
- package/docs/ADR-007-deterministic-ports.md +82 -0
- package/docs/ADR-008-monorepo-tooling.md +60 -0
- package/docs/AWS-Adapter-Architecture.md +496 -0
- package/docs/AWS-Deployment.md +1186 -0
- package/docs/Cloudflare-Deployment-Guide.md +660 -0
- package/docs/Home.md +11 -0
- package/docs/Observability.md +87 -0
- package/docs/PlanIRCv3Websocket.md +489 -0
- package/docs/PlanWebClient.md +451 -0
- package/docs/Release-Process.md +443 -0
- package/package.json +19 -14
- package/packages/aws-adapter/README.md +35 -0
- package/packages/aws-adapter/package.json +52 -0
- package/packages/aws-adapter/src/account-store.ts +121 -0
- package/packages/aws-adapter/src/aws-runtime.ts +871 -0
- package/packages/aws-adapter/src/cdk-table-defs.ts +73 -0
- package/packages/aws-adapter/src/config-loader.ts +126 -0
- package/packages/aws-adapter/src/dynamo-account-store.ts +156 -0
- package/packages/aws-adapter/src/dynamo.ts +61 -0
- package/packages/aws-adapter/src/handlers/connect.ts +44 -0
- package/packages/aws-adapter/src/handlers/default.ts +330 -0
- package/packages/aws-adapter/src/handlers/disconnect.ts +48 -0
- package/packages/aws-adapter/src/handlers/index.ts +293 -0
- package/packages/aws-adapter/src/handlers/ping-checker.ts +217 -0
- package/packages/aws-adapter/src/handlers/state.ts +13 -0
- package/packages/aws-adapter/src/handlers/sweeper.ts +84 -0
- package/packages/aws-adapter/src/index.ts +74 -0
- package/packages/aws-adapter/src/message-store.ts +34 -0
- package/packages/aws-adapter/src/serialize.ts +283 -0
- package/packages/aws-adapter/src/tables.ts +53 -0
- package/packages/aws-adapter/tests/account-store-dynamo.test.ts +171 -0
- package/packages/aws-adapter/tests/account-store.test.ts +280 -0
- package/packages/aws-adapter/tests/aws-harness.ts +408 -0
- package/packages/aws-adapter/tests/aws-integration.test.ts +17 -0
- package/packages/aws-adapter/tests/aws-runtime.test.ts +654 -0
- package/packages/aws-adapter/tests/config-loader.test.ts +213 -0
- package/packages/aws-adapter/tests/disconnect-fanout.test.ts +336 -0
- package/packages/aws-adapter/tests/dynamo.test.ts +57 -0
- package/packages/aws-adapter/tests/global-setup.ts +301 -0
- package/packages/aws-adapter/tests/gone-exception.test.ts +271 -0
- package/packages/aws-adapter/tests/handlers.test.ts +473 -0
- package/packages/aws-adapter/tests/message-store.test.ts +55 -0
- package/packages/aws-adapter/tests/ping-checker.test.ts +571 -0
- package/packages/aws-adapter/tests/serialize.test.ts +249 -0
- package/packages/aws-adapter/tests/smoke.test.ts +48 -0
- package/packages/aws-adapter/tests/sweeper.test.ts +420 -0
- package/packages/aws-adapter/tests/tables.test.ts +74 -0
- package/packages/aws-adapter/tests/transactions.test.ts +446 -0
- package/packages/aws-adapter/tsconfig.build.json +16 -0
- package/packages/aws-adapter/tsconfig.test.json +14 -0
- package/packages/aws-adapter/vitest.config.ts +23 -0
- package/packages/cf-adapter/package.json +2 -1
- package/packages/cf-adapter/src/cf-runtime.ts +42 -22
- package/packages/cf-adapter/src/channel-do.ts +40 -1
- package/packages/cf-adapter/src/channel-registry-do.ts +58 -0
- package/packages/cf-adapter/src/config-loader.ts +135 -0
- package/packages/cf-adapter/src/connection-do.ts +119 -0
- package/packages/cf-adapter/src/env.ts +27 -1
- package/packages/cf-adapter/src/index.ts +2 -1
- package/packages/cf-adapter/tests/cf-harness.ts +2 -2
- package/packages/cf-adapter/tests/cf-integration.test.ts +2 -2
- package/packages/cf-adapter/tests/cf-runtime.test.ts +91 -0
- package/packages/cf-adapter/tests/config-loader.test.ts +149 -0
- package/packages/cf-adapter/tests/connection-do.test.ts +82 -0
- package/packages/cf-adapter/tests/worker/main.ts +2 -1
- package/packages/cf-adapter/tests/worker/stubs/channel-stub.ts +7 -1
- package/packages/cf-adapter/wrangler.test.toml +10 -1
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/in-memory-runtime/src/in-memory-runtime.ts +107 -16
- package/packages/in-memory-runtime/src/index.ts +1 -1
- package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +134 -0
- package/packages/irc-core/package.json +13 -2
- package/packages/irc-core/src/admission.ts +216 -0
- package/packages/irc-core/src/caps/capabilities.ts +1 -0
- package/packages/irc-core/src/case-fold.ts +64 -0
- package/packages/irc-core/src/cloak.ts +81 -0
- package/packages/irc-core/src/commands/cap.ts +1 -2
- package/packages/irc-core/src/commands/chathistory.ts +305 -0
- package/packages/irc-core/src/commands/index.ts +9 -0
- package/packages/irc-core/src/commands/invite.ts +6 -9
- package/packages/irc-core/src/commands/isupport.ts +1 -1
- package/packages/irc-core/src/commands/join.ts +63 -10
- package/packages/irc-core/src/commands/kick.ts +4 -11
- package/packages/irc-core/src/commands/list.ts +5 -4
- package/packages/irc-core/src/commands/mode.ts +5 -9
- package/packages/irc-core/src/commands/motd-lines.ts +127 -0
- package/packages/irc-core/src/commands/motd.ts +6 -110
- package/packages/irc-core/src/commands/oper.ts +151 -0
- package/packages/irc-core/src/commands/privmsg.ts +24 -0
- package/packages/irc-core/src/commands/registration.ts +79 -21
- package/packages/irc-core/src/commands/sasl.ts +251 -0
- package/packages/irc-core/src/commands/tagmsg.ts +205 -0
- package/packages/irc-core/src/config.ts +270 -0
- package/packages/irc-core/src/flood-control.ts +175 -0
- package/packages/irc-core/src/index.ts +7 -0
- package/packages/irc-core/src/ports.ts +719 -0
- package/packages/irc-core/src/protocol/base64.ts +16 -0
- package/packages/irc-core/src/protocol/index.ts +2 -1
- package/packages/irc-core/src/protocol/numerics.ts +11 -0
- package/packages/irc-core/src/protocol/parser.ts +27 -2
- package/packages/irc-core/src/state/connection.ts +41 -0
- package/packages/irc-core/src/types.ts +88 -2
- package/packages/irc-core/stryker.commands.conf.json +41 -0
- package/packages/irc-core/stryker.protocol.conf.json +26 -0
- package/packages/irc-core/tests/account-store.test.ts +88 -0
- package/packages/irc-core/tests/admission.test.ts +229 -0
- package/packages/irc-core/tests/caps/capabilities.test.ts +22 -0
- package/packages/irc-core/tests/case-fold.test.ts +80 -0
- package/packages/irc-core/tests/cloak.test.ts +78 -0
- package/packages/irc-core/tests/commands/chathistory.test.ts +996 -0
- package/packages/irc-core/tests/commands/join.test.ts +246 -2
- package/packages/irc-core/tests/commands/kick.test.ts +15 -0
- package/packages/irc-core/tests/commands/oper.test.ts +257 -0
- package/packages/irc-core/tests/commands/privmsg.test.ts +146 -2
- package/packages/irc-core/tests/commands/registration.test.ts +380 -20
- package/packages/irc-core/tests/commands/sasl.test.ts +536 -0
- package/packages/irc-core/tests/commands/tagmsg.test.ts +688 -0
- package/packages/irc-core/tests/commands/who.test.ts +22 -4
- package/packages/irc-core/tests/commands/whois.test.ts +8 -1
- package/packages/irc-core/tests/config.test.ts +721 -0
- package/packages/irc-core/tests/flood-control.test.ts +394 -0
- package/packages/irc-core/tests/message-store.test.ts +530 -0
- package/packages/irc-core/tests/parser.test.ts +44 -1
- package/packages/irc-core/tests/ports.test.ts +263 -0
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +186 -44
- package/packages/irc-server/src/dispatch.ts +89 -5
- package/packages/irc-server/src/index.ts +2 -0
- package/packages/irc-server/src/routing.ts +160 -0
- package/packages/irc-server/tests/actor.test.ts +690 -15
- package/packages/irc-server/tests/dispatch.test.ts +84 -0
- package/packages/irc-server/tests/routing.test.ts +204 -0
- package/packages/irc-test-support/README.md +32 -0
- package/packages/irc-test-support/package.json +37 -0
- package/packages/{cf-adapter/tests/integration → irc-test-support/src}/in-memory-harness.ts +6 -5
- package/packages/irc-test-support/src/index.ts +24 -0
- package/packages/{cf-adapter/tests/integration/harness.test.ts → irc-test-support/tests/in-memory-harness.test.ts} +1 -1
- package/packages/{cf-adapter/tests/integration/scenarios.test.ts → irc-test-support/tests/in-memory-scenarios.test.ts} +1 -2
- package/packages/irc-test-support/tests/smoke.test.ts +11 -0
- package/packages/irc-test-support/tsconfig.build.json +16 -0
- package/packages/irc-test-support/tsconfig.test.json +14 -0
- package/packages/irc-test-support/vitest.config.ts +20 -0
- package/pnpm-workspace.yaml +1 -0
- package/tools/ci-hardening/package.json +30 -0
- package/tools/ci-hardening/src/index.ts +6 -0
- package/tools/ci-hardening/src/validate.ts +103 -0
- package/tools/ci-hardening/tests/__no_thresholds__.txt +11 -0
- package/tools/ci-hardening/tests/__partial_thresholds__.txt +16 -0
- package/tools/ci-hardening/tests/__stryker_bad__.json +4 -0
- package/tools/ci-hardening/tests/validate.test.ts +177 -0
- package/tools/ci-hardening/tsconfig.build.json +12 -0
- package/tools/ci-hardening/tsconfig.test.json +10 -0
- package/tools/ci-hardening/vitest.config.ts +21 -0
- package/tools/seed-aws-accounts.ts +80 -0
- package/tools/tcp-ws-forwarder/package.json +1 -1
- package/tools/tcp-ws-forwarder/src/forwarder.ts +34 -23
- package/tools/tcp-ws-forwarder/src/logger.ts +155 -0
- package/tools/tcp-ws-forwarder/src/main.ts +33 -14
- package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +86 -0
- package/tools/tcp-ws-forwarder/tests/logger.test.ts +136 -0
- package/packages/cf-adapter/tests/integration/index.ts +0 -17
- /package/packages/{cf-adapter/tests/integration → irc-test-support/src}/harness.ts +0 -0
- /package/packages/{cf-adapter/tests/integration → irc-test-support/src}/scenarios.ts +0 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure unit tests for `src/serialize.ts`.
|
|
3
|
+
*
|
|
4
|
+
* No DynamoDB calls — these tests assert the irc-core → marshalled-row
|
|
5
|
+
* transforms are correct and round-trip-safe. The DynamoDB-backed
|
|
6
|
+
* runtime tests live in `aws-runtime.test.ts`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
type ChannelState,
|
|
11
|
+
type ConnectionState,
|
|
12
|
+
createChannel,
|
|
13
|
+
createConnection,
|
|
14
|
+
} from '@serverless-ircd/irc-core';
|
|
15
|
+
import { describe, expect, it } from 'vitest';
|
|
16
|
+
import {
|
|
17
|
+
marshalChannelMember,
|
|
18
|
+
marshalChannelMeta,
|
|
19
|
+
marshalConnection,
|
|
20
|
+
marshalNick,
|
|
21
|
+
unmarshalChannelMember,
|
|
22
|
+
unmarshalChannelMeta,
|
|
23
|
+
unmarshalConnection,
|
|
24
|
+
unmarshalNick,
|
|
25
|
+
} from '../src/serialize.js';
|
|
26
|
+
|
|
27
|
+
describe('marshalConnection / unmarshalConnection', () => {
|
|
28
|
+
it('round-trips a pre-registration connection with no optional fields', () => {
|
|
29
|
+
const state = createConnection({ id: 'c1', connectedSince: 100 });
|
|
30
|
+
const row = marshalConnection(state, 100);
|
|
31
|
+
const restored = unmarshalConnection(row);
|
|
32
|
+
expectRestoredConnection(restored, state);
|
|
33
|
+
expect(row.idleSince).toBe(100);
|
|
34
|
+
expect(row.version).toBe(1);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('round-trips a fully-registered connection with every optional field set', () => {
|
|
38
|
+
const state = createConnection({ id: 'c1', connectedSince: 100 });
|
|
39
|
+
state.nick = 'Alice';
|
|
40
|
+
state.user = 'alice';
|
|
41
|
+
state.host = 'example.com';
|
|
42
|
+
state.realname = 'Alice In Chains';
|
|
43
|
+
state.passAttempt = 'hunter2';
|
|
44
|
+
state.account = 'alice-acct';
|
|
45
|
+
state.away = 'back later';
|
|
46
|
+
state.saslMech = 'PLAIN';
|
|
47
|
+
state.saslBuffer = 'partial';
|
|
48
|
+
state.caps.add('echo-message');
|
|
49
|
+
state.caps.add('batch');
|
|
50
|
+
state.joinedChannels.add('#foo');
|
|
51
|
+
state.joinedChannels.add('#bar');
|
|
52
|
+
state.userModes.invisible = true;
|
|
53
|
+
|
|
54
|
+
const row = marshalConnection(state, 999);
|
|
55
|
+
const restored = unmarshalConnection(row);
|
|
56
|
+
expectRestoredConnection(restored, state);
|
|
57
|
+
expect(row.idleSince).toBe(999);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('stores caps as an array and joinedChannels as a Set (for atomic ADD/DELETE)', () => {
|
|
61
|
+
const state = createConnection({ id: 'c1', connectedSince: 0 });
|
|
62
|
+
state.caps.add('echo-message');
|
|
63
|
+
state.joinedChannels.add('#chan');
|
|
64
|
+
const row = marshalConnection(state, 0);
|
|
65
|
+
expect(Array.isArray(row.caps)).toBe(true);
|
|
66
|
+
expect(row.joinedChannels).toBeInstanceOf(Set);
|
|
67
|
+
expect((row.joinedChannels as Set<string>).has('#chan')).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('omits joinedChannels from the marshalled row when the set is empty (DynamoDB rejects empty sets)', () => {
|
|
71
|
+
const state = createConnection({ id: 'c1', connectedSince: 0 });
|
|
72
|
+
const row = marshalConnection(state, 0);
|
|
73
|
+
expect(row.joinedChannels).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('round-trips a connection whose joinedChannels started empty (attribute absent → empty Set)', () => {
|
|
77
|
+
const state = createConnection({ id: 'c1', connectedSince: 0 });
|
|
78
|
+
const row = marshalConnection(state, 0);
|
|
79
|
+
const restored = unmarshalConnection(row);
|
|
80
|
+
expect(restored.joinedChannels).toBeInstanceOf(Set);
|
|
81
|
+
expect(restored.joinedChannels.size).toBe(0);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('rejects a row whose version is newer than the supported schema', () => {
|
|
85
|
+
const state = createConnection({ id: 'c1', connectedSince: 0 });
|
|
86
|
+
const row = marshalConnection(state, 0);
|
|
87
|
+
const future = { ...row, version: 9999 };
|
|
88
|
+
expect(() => unmarshalConnection(future)).toThrow(/newer than supported/u);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('preserves the connection id as the row primary key', () => {
|
|
92
|
+
const state = createConnection({ id: 'c-fixed', connectedSince: 0 });
|
|
93
|
+
const row = marshalConnection(state, 0);
|
|
94
|
+
expect(row.connectionId).toBe('c-fixed');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('marshalNick / unmarshalNick', () => {
|
|
99
|
+
it('round-trips a nick reservation row', () => {
|
|
100
|
+
const row = marshalNick('Alice', 'c1', 123);
|
|
101
|
+
expect(row.nickLower).toBe('alice');
|
|
102
|
+
expect(row.nick).toBe('Alice');
|
|
103
|
+
expect(row.connectionId).toBe('c1');
|
|
104
|
+
expect(row.reservedAt).toBe(123);
|
|
105
|
+
expect(unmarshalNick(row)).toEqual({ nick: 'Alice', connectionId: 'c1' });
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('lowercases the PK but preserves display case', () => {
|
|
109
|
+
const row = marshalNick('BoB', 'c2', 0);
|
|
110
|
+
expect(row.nickLower).toBe('bob');
|
|
111
|
+
expect(row.nick).toBe('BoB');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('marshalChannelMeta / unmarshalChannelMeta', () => {
|
|
116
|
+
it('round-trips an empty channel with no topic', () => {
|
|
117
|
+
const chan = createChannel({
|
|
118
|
+
name: '#Foo',
|
|
119
|
+
nameLower: '#foo',
|
|
120
|
+
createdAt: 42,
|
|
121
|
+
});
|
|
122
|
+
const row = marshalChannelMeta(chan);
|
|
123
|
+
expect(row.channelName).toBe('#foo');
|
|
124
|
+
expect(row.displayName).toBe('#Foo');
|
|
125
|
+
expect(row.topicText).toBeUndefined();
|
|
126
|
+
const members = new Map();
|
|
127
|
+
const restored = unmarshalChannelMeta(row, members);
|
|
128
|
+
expectChannelEqual(restored, chan);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('round-trips a channel with topic, modes, banMasks, and pendingInvites', () => {
|
|
132
|
+
const chan = createChannel({
|
|
133
|
+
name: '#Foo',
|
|
134
|
+
nameLower: '#foo',
|
|
135
|
+
createdAt: 1,
|
|
136
|
+
});
|
|
137
|
+
chan.topic = { text: 'welcome', setter: 'alice', setAt: 100 };
|
|
138
|
+
chan.modes.inviteOnly = true;
|
|
139
|
+
chan.modes.topicLock = true;
|
|
140
|
+
chan.modes.key = 'sekret';
|
|
141
|
+
chan.modes.limit = 25;
|
|
142
|
+
chan.banMasks.add('*!bad@*');
|
|
143
|
+
chan.pendingInvites.add('bob');
|
|
144
|
+
|
|
145
|
+
const row = marshalChannelMeta(chan);
|
|
146
|
+
expect(row.topicText).toBe('welcome');
|
|
147
|
+
expect(row.topicSetter).toBe('alice');
|
|
148
|
+
expect(row.topicSetAt).toBe(100);
|
|
149
|
+
expect(row.modes.inviteOnly).toBe(true);
|
|
150
|
+
expect(row.modes.key).toBe('sekret');
|
|
151
|
+
expect(row.modes.limit).toBe(25);
|
|
152
|
+
expect(row.banMasks).toEqual(['*!bad@*']);
|
|
153
|
+
expect(row.pendingInvites).toEqual(['bob']);
|
|
154
|
+
|
|
155
|
+
const restored = unmarshalChannelMeta(row, new Map());
|
|
156
|
+
expectChannelEqual(restored, chan);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('serializes banMasks and pendingInvites as arrays', () => {
|
|
160
|
+
const chan = createChannel({ name: '#x', nameLower: '#x', createdAt: 0 });
|
|
161
|
+
chan.banMasks.add('a');
|
|
162
|
+
chan.banMasks.add('b');
|
|
163
|
+
chan.pendingInvites.add('x');
|
|
164
|
+
const row = marshalChannelMeta(chan);
|
|
165
|
+
expect(Array.isArray(row.banMasks)).toBe(true);
|
|
166
|
+
expect(Array.isArray(row.pendingInvites)).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('omits topic fields when the channel has no topic', () => {
|
|
170
|
+
const chan = createChannel({ name: '#x', nameLower: '#x', createdAt: 0 });
|
|
171
|
+
const row = marshalChannelMeta(chan);
|
|
172
|
+
expect(row.topicText).toBeUndefined();
|
|
173
|
+
expect(row.topicSetter).toBeUndefined();
|
|
174
|
+
expect(row.topicSetAt).toBeUndefined();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('rehydrates members from the supplied map', () => {
|
|
178
|
+
const chan = createChannel({ name: '#x', nameLower: '#x', createdAt: 0 });
|
|
179
|
+
const row = marshalChannelMeta(chan);
|
|
180
|
+
const members = new Map([['c1', { conn: 'c1', nick: 'alice', op: true, voice: false }]]);
|
|
181
|
+
const restored = unmarshalChannelMeta(row, members);
|
|
182
|
+
expect(restored.members.get('c1')).toEqual({
|
|
183
|
+
conn: 'c1',
|
|
184
|
+
nick: 'alice',
|
|
185
|
+
op: true,
|
|
186
|
+
voice: false,
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('marshalChannelMember / unmarshalChannelMember', () => {
|
|
192
|
+
it('round-trips a roster entry', () => {
|
|
193
|
+
const entry = { conn: 'c1', nick: 'alice', op: true, voice: false };
|
|
194
|
+
const row = marshalChannelMember('#foo', entry);
|
|
195
|
+
expect(row.channelName).toBe('#foo');
|
|
196
|
+
expect(row.connectionId).toBe('c1');
|
|
197
|
+
expect(row.nick).toBe('alice');
|
|
198
|
+
expect(row.op).toBe(true);
|
|
199
|
+
expect(row.voice).toBe(false);
|
|
200
|
+
expect(unmarshalChannelMember(row)).toEqual(entry);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('preserves op/voice=false without coercing to undefined', () => {
|
|
204
|
+
const entry = { conn: 'c1', nick: 'alice', op: false, voice: false };
|
|
205
|
+
const row = marshalChannelMember('#foo', entry);
|
|
206
|
+
expect(row.op).toBe(false);
|
|
207
|
+
expect(row.voice).toBe(false);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// Helpers
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
function expectRestoredConnection(restored: ConnectionState, original: ConnectionState): void {
|
|
216
|
+
expect(restored.id).toBe(original.id);
|
|
217
|
+
expect(restored.registration).toBe(original.registration);
|
|
218
|
+
expect(restored.capNegotiating).toBe(original.capNegotiating);
|
|
219
|
+
expect(restored.caps).toEqual(original.caps);
|
|
220
|
+
expect(restored.joinedChannels).toEqual(original.joinedChannels);
|
|
221
|
+
expect(restored.userModes).toEqual(original.userModes);
|
|
222
|
+
expect(restored.lastSeen).toBe(original.lastSeen);
|
|
223
|
+
expect(restored.connectedSince).toBe(original.connectedSince);
|
|
224
|
+
expect(restored.nick).toBe(original.nick);
|
|
225
|
+
expect(restored.user).toBe(original.user);
|
|
226
|
+
expect(restored.host).toBe(original.host);
|
|
227
|
+
expect(restored.realname).toBe(original.realname);
|
|
228
|
+
expect(restored.passAttempt).toBe(original.passAttempt);
|
|
229
|
+
expect(restored.account).toBe(original.account);
|
|
230
|
+
expect(restored.away).toBe(original.away);
|
|
231
|
+
expect(restored.saslMech).toBe(original.saslMech);
|
|
232
|
+
expect(restored.saslBuffer).toBe(original.saslBuffer);
|
|
233
|
+
// Sets must be re-hydrated as Sets, not arrays.
|
|
234
|
+
expect(restored.caps).toBeInstanceOf(Set);
|
|
235
|
+
expect(restored.joinedChannels).toBeInstanceOf(Set);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function expectChannelEqual(restored: ChannelState, original: ChannelState): void {
|
|
239
|
+
expect(restored.name).toBe(original.name);
|
|
240
|
+
expect(restored.nameLower).toBe(original.nameLower);
|
|
241
|
+
expect(restored.modes).toEqual(original.modes);
|
|
242
|
+
expect(restored.banMasks).toEqual(original.banMasks);
|
|
243
|
+
expect(restored.pendingInvites).toEqual(original.pendingInvites);
|
|
244
|
+
expect(restored.createdAt).toBe(original.createdAt);
|
|
245
|
+
expect(restored.topic).toEqual(original.topic);
|
|
246
|
+
expect(restored.banMasks).toBeInstanceOf(Set);
|
|
247
|
+
expect(restored.pendingInvites).toBeInstanceOf(Set);
|
|
248
|
+
expect(restored.members).toBeInstanceOf(Map);
|
|
249
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smoke test for the global-setup: confirms the DynamoDB Local endpoint
|
|
3
|
+
* responds to a real SDK call when `DDB_AVAILABLE=1`, and that the test
|
|
4
|
+
* suite is skipped gracefully otherwise.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
CreateTableCommand,
|
|
9
|
+
DeleteTableCommand,
|
|
10
|
+
DescribeTableCommand,
|
|
11
|
+
} from '@aws-sdk/client-dynamodb';
|
|
12
|
+
import type { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
|
|
13
|
+
import { describe, expect, it } from 'vitest';
|
|
14
|
+
import { createDynamoDocumentClient } from '../src/index.js';
|
|
15
|
+
|
|
16
|
+
const available = process.env.DDB_AVAILABLE === '1';
|
|
17
|
+
const endpoint = process.env.DYNAMO_ENDPOINT;
|
|
18
|
+
|
|
19
|
+
describe.skipIf(!available)('DynamoDB Local endpoint', () => {
|
|
20
|
+
it('accepts CreateTable + DescribeTable round-trip', async () => {
|
|
21
|
+
if (endpoint === undefined) {
|
|
22
|
+
throw new Error('DDB_AVAILABLE=1 but DYNAMO_ENDPOINT is unset');
|
|
23
|
+
}
|
|
24
|
+
const client = createDynamoDocumentClient({ endpoint });
|
|
25
|
+
const tableName = `aws-adapter-smoke-${Date.now()}`;
|
|
26
|
+
await client.send(
|
|
27
|
+
new CreateTableCommand({
|
|
28
|
+
TableName: tableName,
|
|
29
|
+
AttributeDefinitions: [{ AttributeName: 'pk', AttributeType: 'S' }],
|
|
30
|
+
KeySchema: [{ AttributeName: 'pk', KeyType: 'HASH' }],
|
|
31
|
+
BillingMode: 'PAY_PER_REQUEST',
|
|
32
|
+
}),
|
|
33
|
+
);
|
|
34
|
+
const described = await client.send(new DescribeTableCommand({ TableName: tableName }));
|
|
35
|
+
expect(described.Table?.TableName).toBe(tableName);
|
|
36
|
+
await client.send(new DeleteTableCommand({ TableName: tableName }));
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe.skipIf(available)('DynamoDB Local unavailable', () => {
|
|
41
|
+
it('skips the integration suite', () => {
|
|
42
|
+
expect(process.env.DDB_AVAILABLE ?? '0').toBe('0');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Reference the type so TS doesn't complain about unused import on skip paths.
|
|
47
|
+
export type { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
|
|
48
|
+
void (null as unknown as DynamoDBDocumentClient);
|