serverless-ircd 0.1.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 +47 -0
- package/.github/workflows/deploy-cf.yml +97 -0
- package/LICENSE +29 -0
- package/README.md +323 -0
- package/apps/cf-worker/package.json +37 -0
- package/apps/cf-worker/scripts/smoke.mjs +175 -0
- package/apps/cf-worker/src/worker.ts +59 -0
- package/apps/cf-worker/tests/smoke.test.ts +150 -0
- package/apps/cf-worker/tsconfig.build.json +17 -0
- package/apps/cf-worker/tsconfig.test.json +18 -0
- package/apps/cf-worker/vitest.config.ts +31 -0
- package/apps/cf-worker/wrangler.test.toml +33 -0
- package/apps/cf-worker/wrangler.toml +98 -0
- package/apps/local-cli/package.json +35 -0
- package/apps/local-cli/src/line-scanner.ts +60 -0
- package/apps/local-cli/src/main.ts +145 -0
- package/apps/local-cli/src/motd-file.ts +45 -0
- package/apps/local-cli/src/server.ts +409 -0
- package/apps/local-cli/tests/e2e.test.ts +346 -0
- package/apps/local-cli/tests/id-factory.test.ts +25 -0
- package/apps/local-cli/tests/line-scanner.test.ts +81 -0
- package/apps/local-cli/tests/motd-file.test.ts +71 -0
- package/apps/local-cli/tests/tcp.test.ts +358 -0
- package/apps/local-cli/tsconfig.build.json +17 -0
- package/apps/local-cli/tsconfig.test.json +15 -0
- package/apps/local-cli/vitest.config.ts +24 -0
- package/biome.json +52 -0
- package/package.json +35 -0
- package/packages/cf-adapter/package.json +38 -0
- package/packages/cf-adapter/src/cf-runtime.ts +308 -0
- package/packages/cf-adapter/src/channel-do.ts +374 -0
- package/packages/cf-adapter/src/connection-do.ts +542 -0
- package/packages/cf-adapter/src/env.ts +67 -0
- package/packages/cf-adapter/src/index.ts +38 -0
- package/packages/cf-adapter/src/registry-do.ts +130 -0
- package/packages/cf-adapter/src/serialize.ts +142 -0
- package/packages/cf-adapter/src/sharding.ts +101 -0
- package/packages/cf-adapter/tests/cf-harness.ts +264 -0
- package/packages/cf-adapter/tests/cf-integration.test.ts +73 -0
- package/packages/cf-adapter/tests/cf-runtime.test.ts +527 -0
- package/packages/cf-adapter/tests/channel-do.test.ts +480 -0
- package/packages/cf-adapter/tests/connection-do.test.ts +329 -0
- package/packages/cf-adapter/tests/integration/harness.test.ts +229 -0
- package/packages/cf-adapter/tests/integration/harness.ts +102 -0
- package/packages/cf-adapter/tests/integration/in-memory-harness.ts +242 -0
- package/packages/cf-adapter/tests/integration/index.ts +17 -0
- package/packages/cf-adapter/tests/integration/scenarios.test.ts +20 -0
- package/packages/cf-adapter/tests/integration/scenarios.ts +495 -0
- package/packages/cf-adapter/tests/registry-do.test.ts +335 -0
- package/packages/cf-adapter/tests/sharding.test.ts +100 -0
- package/packages/cf-adapter/tests/worker/main.ts +33 -0
- package/packages/cf-adapter/tests/worker/stubs/channel-stub.ts +50 -0
- package/packages/cf-adapter/tests/worker/stubs/registry-stub.ts +57 -0
- package/packages/cf-adapter/tsconfig.build.json +16 -0
- package/packages/cf-adapter/tsconfig.test.json +18 -0
- package/packages/cf-adapter/vitest.config.ts +32 -0
- package/packages/cf-adapter/wrangler.test.toml +50 -0
- package/packages/in-memory-runtime/package.json +29 -0
- package/packages/in-memory-runtime/src/in-memory-runtime.ts +245 -0
- package/packages/in-memory-runtime/src/index.ts +9 -0
- package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +502 -0
- package/packages/in-memory-runtime/tsconfig.build.json +16 -0
- package/packages/in-memory-runtime/tsconfig.test.json +14 -0
- package/packages/in-memory-runtime/vitest.config.ts +21 -0
- package/packages/irc-core/package.json +29 -0
- package/packages/irc-core/src/caps/capabilities.ts +96 -0
- package/packages/irc-core/src/caps/index.ts +1 -0
- package/packages/irc-core/src/commands/away.ts +82 -0
- package/packages/irc-core/src/commands/cap.ts +257 -0
- package/packages/irc-core/src/commands/chghost.ts +30 -0
- package/packages/irc-core/src/commands/index.ts +40 -0
- package/packages/irc-core/src/commands/invite.ts +156 -0
- package/packages/irc-core/src/commands/isupport.ts +133 -0
- package/packages/irc-core/src/commands/join.ts +309 -0
- package/packages/irc-core/src/commands/kick.ts +162 -0
- package/packages/irc-core/src/commands/list.ts +126 -0
- package/packages/irc-core/src/commands/mode.ts +655 -0
- package/packages/irc-core/src/commands/motd.ts +146 -0
- package/packages/irc-core/src/commands/names.ts +164 -0
- package/packages/irc-core/src/commands/part.ts +118 -0
- package/packages/irc-core/src/commands/ping.ts +47 -0
- package/packages/irc-core/src/commands/privmsg.ts +256 -0
- package/packages/irc-core/src/commands/quit.ts +70 -0
- package/packages/irc-core/src/commands/registration.ts +251 -0
- package/packages/irc-core/src/commands/topic.ts +171 -0
- package/packages/irc-core/src/commands/who.ts +169 -0
- package/packages/irc-core/src/commands/whois.ts +165 -0
- package/packages/irc-core/src/effects.ts +184 -0
- package/packages/irc-core/src/index.ts +20 -0
- package/packages/irc-core/src/ports.ts +153 -0
- package/packages/irc-core/src/protocol/batch.ts +85 -0
- package/packages/irc-core/src/protocol/index.ts +18 -0
- package/packages/irc-core/src/protocol/messages.ts +36 -0
- package/packages/irc-core/src/protocol/numerics.ts +155 -0
- package/packages/irc-core/src/protocol/outbound.ts +145 -0
- package/packages/irc-core/src/protocol/parser.ts +175 -0
- package/packages/irc-core/src/protocol/serializer.ts +71 -0
- package/packages/irc-core/src/state/channel.ts +293 -0
- package/packages/irc-core/src/state/connection.ts +153 -0
- package/packages/irc-core/src/state/index.ts +25 -0
- package/packages/irc-core/src/state/registry.ts +22 -0
- package/packages/irc-core/src/types.ts +83 -0
- package/packages/irc-core/tests/batch.test.ts +79 -0
- package/packages/irc-core/tests/caps/capabilities.test.ts +148 -0
- package/packages/irc-core/tests/commands/away.test.ts +233 -0
- package/packages/irc-core/tests/commands/batch.test.ts +178 -0
- package/packages/irc-core/tests/commands/cap.test.ts +499 -0
- package/packages/irc-core/tests/commands/chghost.test.ts +186 -0
- package/packages/irc-core/tests/commands/echo-message.test.ts +212 -0
- package/packages/irc-core/tests/commands/extended-join.test.ts +147 -0
- package/packages/irc-core/tests/commands/invite-notify.test.ts +160 -0
- package/packages/irc-core/tests/commands/invite.test.ts +321 -0
- package/packages/irc-core/tests/commands/isupport.test.ts +209 -0
- package/packages/irc-core/tests/commands/join.test.ts +687 -0
- package/packages/irc-core/tests/commands/kick.test.ts +316 -0
- package/packages/irc-core/tests/commands/list.test.ts +452 -0
- package/packages/irc-core/tests/commands/mode.test.ts +1048 -0
- package/packages/irc-core/tests/commands/motd.test.ts +322 -0
- package/packages/irc-core/tests/commands/names.test.ts +342 -0
- package/packages/irc-core/tests/commands/part.test.ts +265 -0
- package/packages/irc-core/tests/commands/ping.test.ts +144 -0
- package/packages/irc-core/tests/commands/privmsg.test.ts +665 -0
- package/packages/irc-core/tests/commands/quit.test.ts +220 -0
- package/packages/irc-core/tests/commands/registration.test.ts +599 -0
- package/packages/irc-core/tests/commands/topic.test.ts +337 -0
- package/packages/irc-core/tests/commands/who.test.ts +441 -0
- package/packages/irc-core/tests/commands/whois.test.ts +422 -0
- package/packages/irc-core/tests/effects.test.ts +147 -0
- package/packages/irc-core/tests/message-tags.test.ts +182 -0
- package/packages/irc-core/tests/numerics.test.ts +98 -0
- package/packages/irc-core/tests/outbound.test.ts +232 -0
- package/packages/irc-core/tests/parser.test.ts +162 -0
- package/packages/irc-core/tests/ports.test.ts +101 -0
- package/packages/irc-core/tests/serializer.test.ts +164 -0
- package/packages/irc-core/tests/state/channel.test.ts +351 -0
- package/packages/irc-core/tests/state/connection.test.ts +122 -0
- package/packages/irc-core/tests/state/registry.test.ts +20 -0
- package/packages/irc-core/tests/types.test.ts +127 -0
- package/packages/irc-core/tsconfig.build.json +12 -0
- package/packages/irc-core/tsconfig.test.json +10 -0
- package/packages/irc-core/vitest.config.ts +21 -0
- package/packages/irc-server/package.json +28 -0
- package/packages/irc-server/src/actor.ts +449 -0
- package/packages/irc-server/src/dispatch.ts +120 -0
- package/packages/irc-server/src/index.ts +11 -0
- package/packages/irc-server/src/runtime.ts +61 -0
- package/packages/irc-server/tests/actor.test.ts +816 -0
- package/packages/irc-server/tests/dispatch.test.ts +512 -0
- package/packages/irc-server/tests/runtime.test.ts +52 -0
- package/packages/irc-server/tsconfig.build.json +13 -0
- package/packages/irc-server/tsconfig.test.json +11 -0
- package/packages/irc-server/vitest.config.ts +21 -0
- package/pnpm-workspace.yaml +4 -0
- package/tools/tcp-ws-forwarder/package.json +32 -0
- package/tools/tcp-ws-forwarder/src/forwarder.ts +244 -0
- package/tools/tcp-ws-forwarder/src/line-scanner.ts +55 -0
- package/tools/tcp-ws-forwarder/src/main.ts +114 -0
- package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +618 -0
- package/tools/tcp-ws-forwarder/tests/framing.test.ts +51 -0
- package/tools/tcp-ws-forwarder/tests/line-scanner.test.ts +75 -0
- package/tools/tcp-ws-forwarder/tsconfig.build.json +12 -0
- package/tools/tcp-ws-forwarder/tsconfig.test.json +10 -0
- package/tools/tcp-ws-forwarder/vitest.config.ts +27 -0
- package/tsconfig.base.json +25 -0
- package/turbo.json +29 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { kickReducer } from '../../src/commands/kick';
|
|
3
|
+
import { Effect } from '../../src/effects';
|
|
4
|
+
import type { Effect as EffectType, RawLine } from '../../src/effects';
|
|
5
|
+
import { EmptyMotdProvider, FakeClock, SequentialIdFactory } from '../../src/ports';
|
|
6
|
+
import { type ChannelState, createChannel } from '../../src/state/channel';
|
|
7
|
+
import { type ConnectionState, createConnection } from '../../src/state/connection';
|
|
8
|
+
import { type Ctx, type ServerConfig, buildCtx } from '../../src/types';
|
|
9
|
+
|
|
10
|
+
const serverConfig: ServerConfig = {
|
|
11
|
+
serverName: 'irc.example.com',
|
|
12
|
+
networkName: 'ExampleNet',
|
|
13
|
+
maxChannelsPerUser: 30,
|
|
14
|
+
maxTargetsPerCommand: 10,
|
|
15
|
+
maxListEntries: 50,
|
|
16
|
+
nickLen: 30,
|
|
17
|
+
channelLen: 50,
|
|
18
|
+
topicLen: 390,
|
|
19
|
+
quitMessage: 'Client Quit',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function makeCtx(conn: ConnectionState, clock = new FakeClock(1_000)): Ctx {
|
|
23
|
+
return buildCtx({
|
|
24
|
+
serverConfig,
|
|
25
|
+
clock,
|
|
26
|
+
ids: new SequentialIdFactory(),
|
|
27
|
+
motd: EmptyMotdProvider,
|
|
28
|
+
connection: conn,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function makeConn(id = 'c1', nick = 'alice'): ConnectionState {
|
|
33
|
+
const s = createConnection({ id, connectedSince: 0 });
|
|
34
|
+
s.nick = nick;
|
|
35
|
+
s.user = nick;
|
|
36
|
+
s.host = 'example.com';
|
|
37
|
+
s.realname = nick.charAt(0).toUpperCase() + nick.slice(1);
|
|
38
|
+
s.registration = 'registered';
|
|
39
|
+
return s;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function makeChan(name = '#foo'): ChannelState {
|
|
43
|
+
return createChannel({ name, nameLower: name.toLowerCase(), createdAt: 0 });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Adds a member to the channel roster. */
|
|
47
|
+
function addMember(chan: ChannelState, connId: string, nick: string, op = false): void {
|
|
48
|
+
chan.members.set(connId, { conn: connId, nick, op, voice: false });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const L = (text: string): RawLine => ({ text });
|
|
52
|
+
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// kickReducer — success path
|
|
55
|
+
// ============================================================================
|
|
56
|
+
|
|
57
|
+
describe('kickReducer — success', () => {
|
|
58
|
+
it('broadcasts KICK with reason to the channel and removes the target from the roster', () => {
|
|
59
|
+
const chan = makeChan('#foo');
|
|
60
|
+
addMember(chan, 'c1', 'alice', true);
|
|
61
|
+
addMember(chan, 'c2', 'bob', false);
|
|
62
|
+
const conn = makeConn('c1', 'alice');
|
|
63
|
+
const ctx = makeCtx(conn);
|
|
64
|
+
|
|
65
|
+
const out = kickReducer(
|
|
66
|
+
chan,
|
|
67
|
+
{ command: 'KICK', params: ['#foo', 'bob', 'trolling'], tags: {} },
|
|
68
|
+
ctx,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
expect(out.effects).toEqual<EffectType[]>([
|
|
72
|
+
Effect.broadcast('#foo', [L(':alice!alice@example.com KICK #foo bob :trolling')]),
|
|
73
|
+
Effect.applyChannelDelta('#foo', { memberships: [{ type: 'remove', conn: 'c2' }] }),
|
|
74
|
+
]);
|
|
75
|
+
expect(out.state.members.has('c2')).toBe(false);
|
|
76
|
+
expect(out.state.members.has('c1')).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('broadcasts KICK without a trailing reason when none is supplied', () => {
|
|
80
|
+
const chan = makeChan('#foo');
|
|
81
|
+
addMember(chan, 'c1', 'alice', true);
|
|
82
|
+
addMember(chan, 'c2', 'bob');
|
|
83
|
+
const conn = makeConn('c1', 'alice');
|
|
84
|
+
const ctx = makeCtx(conn);
|
|
85
|
+
|
|
86
|
+
const out = kickReducer(chan, { command: 'KICK', params: ['#foo', 'bob'], tags: {} }, ctx);
|
|
87
|
+
|
|
88
|
+
expect(out.effects).toContainEqual<EffectType>(
|
|
89
|
+
Effect.broadcast('#foo', [L(':alice!alice@example.com KICK #foo bob')]),
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('treats an empty reason param the same as no reason param', () => {
|
|
94
|
+
const chan = makeChan('#foo');
|
|
95
|
+
addMember(chan, 'c1', 'alice', true);
|
|
96
|
+
addMember(chan, 'c2', 'bob');
|
|
97
|
+
const conn = makeConn('c1', 'alice');
|
|
98
|
+
const ctx = makeCtx(conn);
|
|
99
|
+
|
|
100
|
+
const out = kickReducer(chan, { command: 'KICK', params: ['#foo', 'bob', ''], tags: {} }, ctx);
|
|
101
|
+
|
|
102
|
+
expect(out.effects).toContainEqual<EffectType>(
|
|
103
|
+
Effect.broadcast('#foo', [L(':alice!alice@example.com KICK #foo bob')]),
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('includes the target in the KICK broadcast (no except)', () => {
|
|
108
|
+
const chan = makeChan('#foo');
|
|
109
|
+
addMember(chan, 'c1', 'alice', true);
|
|
110
|
+
addMember(chan, 'c2', 'bob');
|
|
111
|
+
const conn = makeConn('c1', 'alice');
|
|
112
|
+
const ctx = makeCtx(conn);
|
|
113
|
+
|
|
114
|
+
const out = kickReducer(chan, { command: 'KICK', params: ['#foo', 'bob'], tags: {} }, ctx);
|
|
115
|
+
|
|
116
|
+
const broadcast = out.effects.find(
|
|
117
|
+
(e): e is Extract<EffectType, { tag: 'Broadcast' }> => e.tag === 'Broadcast',
|
|
118
|
+
);
|
|
119
|
+
expect(broadcast?.except).toBeUndefined();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('resolves the target case-insensitively by nick', () => {
|
|
123
|
+
const chan = makeChan('#foo');
|
|
124
|
+
addMember(chan, 'c1', 'alice', true);
|
|
125
|
+
addMember(chan, 'c2', 'Bob');
|
|
126
|
+
const conn = makeConn('c1', 'alice');
|
|
127
|
+
const ctx = makeCtx(conn);
|
|
128
|
+
|
|
129
|
+
const out = kickReducer(chan, { command: 'KICK', params: ['#foo', 'BOB'], tags: {} }, ctx);
|
|
130
|
+
|
|
131
|
+
expect(out.state.members.has('c2')).toBe(false);
|
|
132
|
+
expect(out.effects).toContainEqual<EffectType>(
|
|
133
|
+
Effect.applyChannelDelta('#foo', { memberships: [{ type: 'remove', conn: 'c2' }] }),
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('updates the kicker lastSeen to ctx.clock.now()', () => {
|
|
138
|
+
const chan = makeChan('#foo');
|
|
139
|
+
addMember(chan, 'c1', 'alice', true);
|
|
140
|
+
addMember(chan, 'c2', 'bob');
|
|
141
|
+
const conn = makeConn('c1', 'alice');
|
|
142
|
+
const clock = new FakeClock(7_500);
|
|
143
|
+
const ctx = makeCtx(conn, clock);
|
|
144
|
+
|
|
145
|
+
kickReducer(chan, { command: 'KICK', params: ['#foo', 'bob'], tags: {} }, ctx);
|
|
146
|
+
|
|
147
|
+
expect(conn.lastSeen).toBe(7_500);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('returns the same state reference (mutation permitted, no copy)', () => {
|
|
151
|
+
const chan = makeChan('#foo');
|
|
152
|
+
addMember(chan, 'c1', 'alice', true);
|
|
153
|
+
addMember(chan, 'c2', 'bob');
|
|
154
|
+
const conn = makeConn('c1', 'alice');
|
|
155
|
+
const ctx = makeCtx(conn);
|
|
156
|
+
|
|
157
|
+
const out = kickReducer(chan, { command: 'KICK', params: ['#foo', 'bob'], tags: {} }, ctx);
|
|
158
|
+
|
|
159
|
+
expect(out.state).toBe(chan);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('falls back to ? as the KICK source when the kicker has no nick (defensive)', () => {
|
|
163
|
+
const chan = makeChan('#foo');
|
|
164
|
+
addMember(chan, 'c1', '?', true);
|
|
165
|
+
addMember(chan, 'c2', 'bob');
|
|
166
|
+
const conn = createConnection({ id: 'c1', connectedSince: 0 });
|
|
167
|
+
const ctx = makeCtx(conn);
|
|
168
|
+
|
|
169
|
+
const out = kickReducer(chan, { command: 'KICK', params: ['#foo', 'bob'], tags: {} }, ctx);
|
|
170
|
+
|
|
171
|
+
expect(out.effects).toContainEqual<EffectType>(
|
|
172
|
+
Effect.broadcast('#foo', [L(':? KICK #foo bob')]),
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// ============================================================================
|
|
178
|
+
// kickReducer — rejections
|
|
179
|
+
// ============================================================================
|
|
180
|
+
|
|
181
|
+
describe('kickReducer — rejections', () => {
|
|
182
|
+
it('emits 461 ERR_NEEDMOREPARAMS when no channel is supplied', () => {
|
|
183
|
+
const chan = makeChan('#foo');
|
|
184
|
+
addMember(chan, 'c1', 'alice', true);
|
|
185
|
+
addMember(chan, 'c2', 'bob');
|
|
186
|
+
const conn = makeConn('c1', 'alice');
|
|
187
|
+
const ctx = makeCtx(conn);
|
|
188
|
+
|
|
189
|
+
const out = kickReducer(chan, { command: 'KICK', params: [], tags: {} }, ctx);
|
|
190
|
+
|
|
191
|
+
expect(out.effects).toEqual<EffectType[]>([
|
|
192
|
+
Effect.send('c1', [L(':irc.example.com 461 alice KICK :Not enough parameters')]),
|
|
193
|
+
]);
|
|
194
|
+
expect(out.state.members.has('c2')).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('emits 461 ERR_NEEDMOREPARAMS when no target is supplied', () => {
|
|
198
|
+
const chan = makeChan('#foo');
|
|
199
|
+
addMember(chan, 'c1', 'alice', true);
|
|
200
|
+
addMember(chan, 'c2', 'bob');
|
|
201
|
+
const conn = makeConn('c1', 'alice');
|
|
202
|
+
const ctx = makeCtx(conn);
|
|
203
|
+
|
|
204
|
+
const out = kickReducer(chan, { command: 'KICK', params: ['#foo'], tags: {} }, ctx);
|
|
205
|
+
|
|
206
|
+
expect(out.effects).toEqual<EffectType[]>([
|
|
207
|
+
Effect.send('c1', [L(':irc.example.com 461 alice KICK :Not enough parameters')]),
|
|
208
|
+
]);
|
|
209
|
+
expect(out.state.members.has('c2')).toBe(true);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('emits 403 ERR_NOSUCHCHANNEL for a channel name without a valid prefix', () => {
|
|
213
|
+
const chan = makeChan('foo');
|
|
214
|
+
const conn = makeConn('c1', 'alice');
|
|
215
|
+
const ctx = makeCtx(conn);
|
|
216
|
+
|
|
217
|
+
const out = kickReducer(chan, { command: 'KICK', params: ['foo', 'bob'], tags: {} }, ctx);
|
|
218
|
+
|
|
219
|
+
expect(out.effects).toEqual<EffectType[]>([
|
|
220
|
+
Effect.send('c1', [L(':irc.example.com 403 alice foo :No such channel')]),
|
|
221
|
+
]);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('emits 403 ERR_NOSUCHCHANNEL for a channel name containing a comma', () => {
|
|
225
|
+
const chan = makeChan('#foo');
|
|
226
|
+
const conn = makeConn('c1', 'alice');
|
|
227
|
+
const ctx = makeCtx(conn);
|
|
228
|
+
|
|
229
|
+
const out = kickReducer(chan, { command: 'KICK', params: ['#foo,bar', 'bob'], tags: {} }, ctx);
|
|
230
|
+
|
|
231
|
+
expect(out.effects).toEqual<EffectType[]>([
|
|
232
|
+
Effect.send('c1', [L(':irc.example.com 403 alice #foo,bar :No such channel')]),
|
|
233
|
+
]);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('emits 403 ERR_NOSUCHCHANNEL for an empty channel name', () => {
|
|
237
|
+
const chan = makeChan('#foo');
|
|
238
|
+
const conn = makeConn('c1', 'alice');
|
|
239
|
+
const ctx = makeCtx(conn);
|
|
240
|
+
|
|
241
|
+
const out = kickReducer(chan, { command: 'KICK', params: ['', 'bob'], tags: {} }, ctx);
|
|
242
|
+
|
|
243
|
+
expect(out.effects).toEqual<EffectType[]>([
|
|
244
|
+
Effect.send('c1', [L(':irc.example.com 403 alice :No such channel')]),
|
|
245
|
+
]);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('emits 403 ERR_NOSUCHCHANNEL for a channel name exceeding the length cap', () => {
|
|
249
|
+
const longName = `#${'a'.repeat(50)}`;
|
|
250
|
+
const chan = makeChan(longName);
|
|
251
|
+
const conn = makeConn('c1', 'alice');
|
|
252
|
+
const ctx = makeCtx(conn);
|
|
253
|
+
|
|
254
|
+
const out = kickReducer(chan, { command: 'KICK', params: [longName, 'bob'], tags: {} }, ctx);
|
|
255
|
+
|
|
256
|
+
expect(out.effects).toEqual<EffectType[]>([
|
|
257
|
+
Effect.send('c1', [L(`:irc.example.com 403 alice ${longName} :No such channel`)]),
|
|
258
|
+
]);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('emits 442 ERR_NOTONCHANNEL when the kicker is not on the channel', () => {
|
|
262
|
+
const chan = makeChan('#foo');
|
|
263
|
+
addMember(chan, 'c2', 'bob');
|
|
264
|
+
const conn = makeConn('c1', 'alice');
|
|
265
|
+
const ctx = makeCtx(conn);
|
|
266
|
+
|
|
267
|
+
const out = kickReducer(chan, { command: 'KICK', params: ['#foo', 'bob'], tags: {} }, ctx);
|
|
268
|
+
|
|
269
|
+
expect(out.effects).toEqual<EffectType[]>([
|
|
270
|
+
Effect.send('c1', [L(":irc.example.com 442 alice #foo :You're not on that channel")]),
|
|
271
|
+
]);
|
|
272
|
+
expect(out.state.members.has('c2')).toBe(true);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('emits 482 ERR_CHANOPRIVSNEEDED when the kicker is on the channel but not an op', () => {
|
|
276
|
+
const chan = makeChan('#foo');
|
|
277
|
+
addMember(chan, 'c1', 'alice', false);
|
|
278
|
+
addMember(chan, 'c2', 'bob', false);
|
|
279
|
+
const conn = makeConn('c1', 'alice');
|
|
280
|
+
const ctx = makeCtx(conn);
|
|
281
|
+
|
|
282
|
+
const out = kickReducer(chan, { command: 'KICK', params: ['#foo', 'bob'], tags: {} }, ctx);
|
|
283
|
+
|
|
284
|
+
expect(out.effects).toEqual<EffectType[]>([
|
|
285
|
+
Effect.send('c1', [L(":irc.example.com 482 alice #foo :You're not channel operator")]),
|
|
286
|
+
]);
|
|
287
|
+
expect(out.state.members.has('c2')).toBe(true);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('emits 441 ERR_USERNOTINCHANNEL when the target is not on the channel', () => {
|
|
291
|
+
const chan = makeChan('#foo');
|
|
292
|
+
addMember(chan, 'c1', 'alice', true);
|
|
293
|
+
const conn = makeConn('c1', 'alice');
|
|
294
|
+
const ctx = makeCtx(conn);
|
|
295
|
+
|
|
296
|
+
const out = kickReducer(chan, { command: 'KICK', params: ['#foo', 'ghost'], tags: {} }, ctx);
|
|
297
|
+
|
|
298
|
+
expect(out.effects).toEqual<EffectType[]>([
|
|
299
|
+
Effect.send('c1', [L(':irc.example.com 441 alice ghost #foo :They are not on that channel')]),
|
|
300
|
+
]);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('uses * in numeric replies when the connection has no nick (defensive)', () => {
|
|
304
|
+
const chan = makeChan('#foo');
|
|
305
|
+
addMember(chan, 'c1', '?', true);
|
|
306
|
+
addMember(chan, 'c2', 'bob');
|
|
307
|
+
const conn = createConnection({ id: 'c1', connectedSince: 0 });
|
|
308
|
+
const ctx = makeCtx(conn);
|
|
309
|
+
|
|
310
|
+
const out = kickReducer(chan, { command: 'KICK', params: [], tags: {} }, ctx);
|
|
311
|
+
|
|
312
|
+
expect(out.effects).toEqual<EffectType[]>([
|
|
313
|
+
Effect.send('c1', [L(':irc.example.com 461 * KICK :Not enough parameters')]),
|
|
314
|
+
]);
|
|
315
|
+
});
|
|
316
|
+
});
|