serverless-ircd 0.2.0 → 0.4.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.
Files changed (221) hide show
  1. package/.github/workflows/ci.yml +2 -2
  2. package/.github/workflows/deploy-aws.yml +1 -3
  3. package/.github/workflows/deploy-cf-tcp.yml +87 -0
  4. package/.github/workflows/deploy-cf.yml +1 -1
  5. package/.node-version +1 -0
  6. package/.nvmrc +1 -0
  7. package/CHANGELOG.md +258 -18
  8. package/README.md +72 -30
  9. package/apps/aws-stack/README.md +2 -2
  10. package/apps/aws-stack/bin/aws.ts +7 -0
  11. package/apps/aws-stack/package.json +4 -4
  12. package/apps/aws-stack/src/aws-stack.ts +118 -6
  13. package/apps/aws-stack/tests/stack.test.ts +98 -3
  14. package/apps/cf-tcp-container/Dockerfile +69 -0
  15. package/apps/cf-tcp-container/package.json +34 -0
  16. package/apps/cf-tcp-container/src/config-loader.ts +145 -0
  17. package/apps/cf-tcp-container/src/container-do.ts +38 -0
  18. package/apps/cf-tcp-container/src/container-server.ts +363 -0
  19. package/apps/cf-tcp-container/src/main.ts +77 -0
  20. package/apps/cf-tcp-container/src/persistence.ts +144 -0
  21. package/apps/cf-tcp-container/src/worker.ts +41 -0
  22. package/apps/cf-tcp-container/terraform/provider.tf +24 -0
  23. package/apps/cf-tcp-container/terraform/spectrum.tf +81 -0
  24. package/apps/cf-tcp-container/tests/config-loader.test.ts +217 -0
  25. package/apps/cf-tcp-container/tests/container-server.test.ts +465 -0
  26. package/apps/cf-tcp-container/tests/persistence.test.ts +227 -0
  27. package/apps/cf-tcp-container/tests/tls-e2e.test.ts +275 -0
  28. package/apps/cf-tcp-container/tsconfig.build.json +17 -0
  29. package/apps/cf-tcp-container/tsconfig.test.json +15 -0
  30. package/apps/cf-tcp-container/vitest.config.ts +26 -0
  31. package/apps/cf-tcp-container/wrangler.toml +63 -0
  32. package/apps/cf-worker/package.json +1 -1
  33. package/apps/cf-worker/scripts/smoke.mjs +0 -0
  34. package/apps/cf-worker/src/worker.ts +8 -2
  35. package/apps/cf-worker/tests/smoke.test.ts +1 -0
  36. package/apps/cf-worker/wrangler.test.toml +11 -1
  37. package/apps/cf-worker/wrangler.toml +69 -19
  38. package/apps/local-cli/package.json +1 -1
  39. package/apps/local-cli/src/config-loader.ts +11 -0
  40. package/apps/local-cli/src/main.ts +1 -1
  41. package/apps/local-cli/src/server.ts +46 -3
  42. package/apps/local-cli/tests/e2e.test.ts +52 -0
  43. package/package.json +19 -16
  44. package/packages/aws-adapter/package.json +3 -3
  45. package/packages/aws-adapter/src/account-store.ts +121 -0
  46. package/packages/aws-adapter/src/admission.ts +74 -0
  47. package/packages/aws-adapter/src/aws-runtime.ts +124 -34
  48. package/packages/aws-adapter/src/cdk-table-defs.ts +5 -1
  49. package/packages/aws-adapter/src/config-loader.ts +62 -0
  50. package/packages/aws-adapter/src/dynamo-account-store.ts +95 -0
  51. package/packages/aws-adapter/src/handlers/connect.ts +64 -8
  52. package/packages/aws-adapter/src/handlers/default.ts +43 -2
  53. package/packages/aws-adapter/src/handlers/disconnect.ts +27 -8
  54. package/packages/aws-adapter/src/handlers/index.ts +120 -9
  55. package/packages/aws-adapter/src/handlers/nlb-stream.ts +481 -0
  56. package/packages/aws-adapter/src/handlers/ping-checker.ts +1 -1
  57. package/packages/aws-adapter/src/index.ts +13 -0
  58. package/packages/aws-adapter/tests/account-store-dynamo.test.ts +182 -0
  59. package/packages/aws-adapter/tests/account-store.test.ts +279 -0
  60. package/packages/aws-adapter/tests/admission.test.ts +70 -0
  61. package/packages/aws-adapter/tests/aws-harness.ts +13 -1
  62. package/packages/aws-adapter/tests/config-loader.test.ts +75 -0
  63. package/packages/aws-adapter/tests/connect.test.ts +78 -0
  64. package/packages/aws-adapter/tests/disconnect-fanout.test.ts +343 -0
  65. package/packages/aws-adapter/tests/gone-exception.test.ts +31 -26
  66. package/packages/aws-adapter/tests/handlers.test.ts +194 -47
  67. package/packages/aws-adapter/tests/nlb-stream.test.ts +478 -0
  68. package/packages/aws-adapter/tests/ping-checker.test.ts +34 -29
  69. package/packages/aws-adapter/tests/sweeper.test.ts +25 -18
  70. package/packages/aws-adapter/tests/transactions.test.ts +25 -20
  71. package/packages/cf-adapter/package.json +1 -1
  72. package/packages/cf-adapter/src/cf-runtime.ts +40 -22
  73. package/packages/cf-adapter/src/channel-do.ts +40 -1
  74. package/packages/cf-adapter/src/channel-registry-do.ts +58 -0
  75. package/packages/cf-adapter/src/config-loader.ts +62 -0
  76. package/packages/cf-adapter/src/connection-do.ts +181 -31
  77. package/packages/cf-adapter/src/d1-account-store.ts +198 -0
  78. package/packages/cf-adapter/src/env.ts +58 -8
  79. package/packages/cf-adapter/src/index.ts +11 -9
  80. package/packages/cf-adapter/tests/cf-harness.ts +11 -1
  81. package/packages/cf-adapter/tests/cf-integration.test.ts +2 -1
  82. package/packages/cf-adapter/tests/cf-runtime.test.ts +91 -0
  83. package/packages/cf-adapter/tests/config-loader.test.ts +22 -0
  84. package/packages/cf-adapter/tests/connection-do-channel-registration.test.ts +37 -0
  85. package/packages/cf-adapter/tests/connection-do-no-batching-reservation.test.ts +52 -0
  86. package/packages/cf-adapter/tests/connection-do-sasl-d1.test.ts +166 -0
  87. package/packages/cf-adapter/tests/connection-do.test.ts +40 -0
  88. package/packages/cf-adapter/tests/d1-account-store.test.ts +226 -0
  89. package/packages/cf-adapter/tests/raw-modules.d.ts +11 -0
  90. package/packages/cf-adapter/tests/worker/main.ts +9 -1
  91. package/packages/cf-adapter/tests/worker/stubs/channel-stub.ts +7 -1
  92. package/packages/cf-adapter/wrangler.test.toml +18 -1
  93. package/packages/in-memory-runtime/package.json +1 -1
  94. package/packages/in-memory-runtime/src/in-memory-runtime.ts +14 -28
  95. package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +19 -0
  96. package/packages/irc-core/package.json +8 -2
  97. package/packages/irc-core/scripts/generate-build-info.mjs +31 -0
  98. package/packages/irc-core/src/caps/capabilities.ts +23 -2
  99. package/packages/irc-core/src/case-fold.ts +64 -0
  100. package/packages/irc-core/src/cloak.ts +1 -1
  101. package/packages/irc-core/src/commands/cap.ts +8 -1
  102. package/packages/irc-core/src/commands/index.ts +11 -0
  103. package/packages/irc-core/src/commands/invite.ts +6 -9
  104. package/packages/irc-core/src/commands/ison.ts +61 -0
  105. package/packages/irc-core/src/commands/isupport.ts +1 -1
  106. package/packages/irc-core/src/commands/join.ts +4 -3
  107. package/packages/irc-core/src/commands/kick.ts +4 -11
  108. package/packages/irc-core/src/commands/list.ts +5 -4
  109. package/packages/irc-core/src/commands/mode.ts +5 -9
  110. package/packages/irc-core/src/commands/motd-lines.ts +127 -0
  111. package/packages/irc-core/src/commands/motd.ts +6 -110
  112. package/packages/irc-core/src/commands/oper.ts +151 -0
  113. package/packages/irc-core/src/commands/quit.ts +12 -0
  114. package/packages/irc-core/src/commands/registration.ts +26 -19
  115. package/packages/irc-core/src/commands/sasl.ts +72 -9
  116. package/packages/irc-core/src/commands/server-info.ts +129 -0
  117. package/packages/irc-core/src/commands/tagmsg.ts +205 -0
  118. package/packages/irc-core/src/commands/userhost.ts +84 -0
  119. package/packages/irc-core/src/commands/whowas.ts +113 -0
  120. package/packages/irc-core/src/config.ts +84 -3
  121. package/packages/irc-core/src/credential-hashing.ts +124 -0
  122. package/packages/irc-core/src/effects.ts +6 -29
  123. package/packages/irc-core/src/index.ts +3 -0
  124. package/packages/irc-core/src/ports.ts +240 -7
  125. package/packages/irc-core/src/protocol/numerics.ts +14 -8
  126. package/packages/irc-core/src/types.ts +60 -1
  127. package/packages/irc-core/tests/account-store.test.ts +131 -0
  128. package/packages/irc-core/tests/caps/capabilities.test.ts +4 -3
  129. package/packages/irc-core/tests/case-fold.test.ts +80 -0
  130. package/packages/irc-core/tests/commands/cap.test.ts +33 -1
  131. package/packages/irc-core/tests/commands/ison.test.ts +166 -0
  132. package/packages/irc-core/tests/commands/kick.test.ts +15 -0
  133. package/packages/irc-core/tests/commands/oper.test.ts +257 -0
  134. package/packages/irc-core/tests/commands/quit.test.ts +69 -2
  135. package/packages/irc-core/tests/commands/registration.test.ts +256 -19
  136. package/packages/irc-core/tests/commands/sasl.test.ts +118 -10
  137. package/packages/irc-core/tests/commands/server-info.test.ts +274 -0
  138. package/packages/irc-core/tests/commands/tagmsg.test.ts +662 -0
  139. package/packages/irc-core/tests/commands/userhost.test.ts +264 -0
  140. package/packages/irc-core/tests/commands/who.test.ts +22 -4
  141. package/packages/irc-core/tests/commands/whois.test.ts +8 -1
  142. package/packages/irc-core/tests/commands/whowas.test.ts +312 -0
  143. package/packages/irc-core/tests/config.test.ts +170 -1
  144. package/packages/irc-core/tests/credential-hashing.test.ts +170 -0
  145. package/packages/irc-core/tests/effects.test.ts +0 -27
  146. package/packages/irc-core/tests/nick-history-store.test.ts +162 -0
  147. package/packages/irc-core/tests/numerics.test.ts +12 -0
  148. package/packages/irc-core/tests/types.test.ts +35 -1
  149. package/packages/irc-core/tsconfig.build.json +1 -1
  150. package/packages/irc-core/tsconfig.test.json +1 -1
  151. package/packages/irc-server/package.json +1 -1
  152. package/packages/irc-server/src/actor.ts +182 -14
  153. package/packages/irc-server/src/dispatch.ts +0 -3
  154. package/packages/irc-server/src/index.ts +10 -2
  155. package/packages/irc-server/src/routing.ts +21 -0
  156. package/packages/irc-server/src/transport.ts +101 -0
  157. package/packages/irc-server/tests/actor.test.ts +617 -1
  158. package/packages/irc-server/tests/dispatch.test.ts +0 -17
  159. package/packages/irc-server/tests/routing.test.ts +7 -0
  160. package/packages/irc-server/tests/transport.test.ts +230 -0
  161. package/packages/irc-test-support/package.json +1 -1
  162. package/packages/irc-test-support/src/harness.ts +44 -9
  163. package/packages/irc-test-support/src/in-memory-harness.ts +78 -13
  164. package/packages/irc-test-support/src/index.ts +3 -0
  165. package/packages/irc-test-support/src/scenarios.ts +132 -2
  166. package/packages/irc-test-support/tests/in-memory-harness.test.ts +2 -1
  167. package/packages/irc-test-support/tests/in-memory-scenarios.test.ts +23 -9
  168. package/pnpm-workspace.yaml +9 -0
  169. package/tools/ci-hardening/package.json +1 -1
  170. package/tools/package.json +4 -0
  171. package/tools/seed-aws-accounts.ts +79 -0
  172. package/tools/seed-cf-accounts.ts +104 -0
  173. package/tools/tcp-ws-forwarder/package.json +1 -1
  174. package/AGENTS.md +0 -5
  175. package/MOTD.txt +0 -3
  176. package/PLAN-FIXES.md +0 -358
  177. package/PLAN.md +0 -420
  178. package/dashboards/cloudwatch-irc.json +0 -139
  179. package/docs/AWS-Adapter-Architecture.md +0 -494
  180. package/docs/AWS-Deployment.md +0 -1107
  181. package/docs/Cloudflare-Deployment-Guide.md +0 -660
  182. package/docs/Home.md +0 -1
  183. package/docs/Observability.md +0 -87
  184. package/docs/PlanIRCv3Websocket.md +0 -489
  185. package/docs/PlanWebClient.md +0 -451
  186. package/docs/Release-Process.md +0 -440
  187. package/progress.md +0 -107
  188. package/tickets.md +0 -2485
  189. package/webircgateway/LICENSE +0 -201
  190. package/webircgateway/Makefile +0 -44
  191. package/webircgateway/README.md +0 -134
  192. package/webircgateway/config.conf.example +0 -135
  193. package/webircgateway/go.mod +0 -16
  194. package/webircgateway/go.sum +0 -89
  195. package/webircgateway/main.go +0 -118
  196. package/webircgateway/pkg/dnsbl/dnsbl.go +0 -121
  197. package/webircgateway/pkg/identd/identd.go +0 -86
  198. package/webircgateway/pkg/identd/rpcclient.go +0 -59
  199. package/webircgateway/pkg/irc/isupport.go +0 -56
  200. package/webircgateway/pkg/irc/message.go +0 -217
  201. package/webircgateway/pkg/irc/state.go +0 -79
  202. package/webircgateway/pkg/proxy/proxy.go +0 -129
  203. package/webircgateway/pkg/proxy/server.go +0 -237
  204. package/webircgateway/pkg/recaptcha/recaptcha.go +0 -59
  205. package/webircgateway/pkg/webircgateway/client.go +0 -741
  206. package/webircgateway/pkg/webircgateway/client_command_handlers.go +0 -495
  207. package/webircgateway/pkg/webircgateway/config.go +0 -385
  208. package/webircgateway/pkg/webircgateway/gateway.go +0 -278
  209. package/webircgateway/pkg/webircgateway/gateway_utils.go +0 -133
  210. package/webircgateway/pkg/webircgateway/hooks.go +0 -152
  211. package/webircgateway/pkg/webircgateway/letsencrypt.go +0 -41
  212. package/webircgateway/pkg/webircgateway/messagetags.go +0 -103
  213. package/webircgateway/pkg/webircgateway/transport_kiwiirc.go +0 -206
  214. package/webircgateway/pkg/webircgateway/transport_sockjs.go +0 -107
  215. package/webircgateway/pkg/webircgateway/transport_tcp.go +0 -113
  216. package/webircgateway/pkg/webircgateway/transport_websocket.go +0 -126
  217. package/webircgateway/pkg/webircgateway/utils.go +0 -147
  218. package/webircgateway/plugins/example/plugin.go +0 -11
  219. package/webircgateway/plugins/stats/plugin.go +0 -52
  220. package/webircgateway/staticcheck.conf +0 -1
  221. package/webircgateway/webircgateway.svg +0 -3
@@ -0,0 +1,81 @@
1
+ ###############################################################################
2
+ # Cloudflare Spectrum config for the ServerlessIRCd TCP+TLS transport.
3
+ #
4
+ # Spectrum terminates TLS on port 6697 and forwards plaintext TCP to the
5
+ # container origin. The origin is the Cloudflare Container (or external
6
+ # compute) running the IRC core on port 6667.
7
+ #
8
+ # Usage:
9
+ # terraform init
10
+ # terraform apply \
11
+ # -var zone_id=YOUR_ZONE_ID \
12
+ # -var origin_address=container-origin.example.com \
13
+ # -var hostname=irc.example.com
14
+ #
15
+ # The `tls = "flexible"` mode means TLS is terminated by Spectrum and the
16
+ # connection to the origin is plaintext (matching the container's plaintext
17
+ # TCP listener). For mTLS passthrough (SASL EXTERNAL via client cert), set
18
+ # `tls = "full"` and configure the origin to present its own certificate.
19
+ ###############################################################################
20
+
21
+ variable "zone_id" {
22
+ type = string
23
+ description = "Cloudflare zone ID for the domain (e.g. example.com)."
24
+ }
25
+
26
+ variable "origin_address" {
27
+ type = string
28
+ description = "Hostname or IP of the TCP origin (CF Container or external compute)."
29
+ }
30
+
31
+ variable "origin_port" {
32
+ type = number
33
+ default = 6667
34
+ description = "Port the TCP origin listens on (plaintext IRC)."
35
+ }
36
+
37
+ variable "hostname" {
38
+ type = string
39
+ default = "irc.example.com"
40
+ description = "Public hostname clients connect to (irc+tls on :6697)."
41
+ }
42
+
43
+ variable "spectrum_port" {
44
+ type = number
45
+ default = 6697
46
+ description = "Public port Spectrum listens on (IRC-over-TLS)."
47
+ }
48
+
49
+ variable "tls_mode" {
50
+ type = string
51
+ default = "flexible"
52
+ description = "Spectrum TLS mode: flexible (TLS→CF, plain→origin) or full (TLS end-to-end)."
53
+
54
+ validation {
55
+ condition = contains(["flexible", "full", "strict"], var.tls_mode)
56
+ error_message = "tls_mode must be one of: flexible, full, strict."
57
+ }
58
+ }
59
+
60
+ resource "cloudflare_spectrum_application" "irc_tls" {
61
+ zone_id = var.zone_id
62
+ protocol = "tcp/${var.spectrum_port}"
63
+
64
+ dns {
65
+ type = "CNAME"
66
+ name = var.hostname
67
+ }
68
+
69
+ origin_dns {
70
+ name = var.origin_address
71
+ }
72
+
73
+ origin_port = var.origin_port
74
+ tls = var.tls_mode
75
+ ip_firewall = true
76
+ argo_smart_routing = true
77
+
78
+ # PROXY protocol v1 forwards the real client IP to the origin so the
79
+ # container's admission gate (per-IP limits) sees the actual source.
80
+ proxy_protocol = "v1"
81
+ }
@@ -0,0 +1,217 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { type ContainerConfigEnv, loadContainerConfig } from '../src/config-loader.js';
3
+
4
+ describe('config-loader — IRC server config', () => {
5
+ it('parses required serverName and networkName', () => {
6
+ const cfg = loadContainerConfig({ SERVER_NAME: 'irc.example.com', NETWORK_NAME: 'ExampleNet' });
7
+ expect(cfg.server.serverName).toBe('irc.example.com');
8
+ expect(cfg.server.networkName).toBe('ExampleNet');
9
+ });
10
+
11
+ it('splits MOTD_LINES on newline', () => {
12
+ const cfg = loadContainerConfig({
13
+ SERVER_NAME: 'irc.test',
14
+ NETWORK_NAME: 'TestNet',
15
+ MOTD_LINES: 'line one\nline two\nline three',
16
+ });
17
+ expect(cfg.server.motdLines).toEqual(['line one', 'line two', 'line three']);
18
+ });
19
+
20
+ it('coerces numeric env vars', () => {
21
+ const cfg = loadContainerConfig({
22
+ SERVER_NAME: 'irc.test',
23
+ NETWORK_NAME: 'TestNet',
24
+ MAX_CHANNELS_PER_USER: '5',
25
+ NICK_LEN: '20',
26
+ });
27
+ expect(cfg.server.maxChannelsPerUser).toBe(5);
28
+ expect(cfg.server.nickLen).toBe(20);
29
+ });
30
+
31
+ it('parses SASL accounts from newline-delimited user:password', () => {
32
+ const cfg = loadContainerConfig({
33
+ SERVER_NAME: 'irc.test',
34
+ NETWORK_NAME: 'TestNet',
35
+ SASL_ACCOUNTS: 'alice:secret\nbob:pass2',
36
+ });
37
+ expect(cfg.server.saslAccounts).toEqual([
38
+ { username: 'alice', password: 'secret' },
39
+ { username: 'bob', password: 'pass2' },
40
+ ]);
41
+ });
42
+
43
+ it('applies schema default serverName when unset', () => {
44
+ const cfg = loadContainerConfig({ NETWORK_NAME: 'TestNet' } as ContainerConfigEnv);
45
+ expect(cfg.server.serverName).toBe('irc.example.com');
46
+ });
47
+ });
48
+
49
+ describe('config-loader — container-specific knobs', () => {
50
+ it('defaults tcpPort to 6667', () => {
51
+ const cfg = loadContainerConfig({ SERVER_NAME: 'irc.test', NETWORK_NAME: 'TestNet' });
52
+ expect(cfg.tcpPort).toBe(6667);
53
+ });
54
+
55
+ it('defaults tcpHost to 0.0.0.0 (bind-all for Spectrum origin)', () => {
56
+ const cfg = loadContainerConfig({ SERVER_NAME: 'irc.test', NETWORK_NAME: 'TestNet' });
57
+ expect(cfg.tcpHost).toBe('0.0.0.0');
58
+ });
59
+
60
+ it('reads TCP_PORT override', () => {
61
+ const cfg = loadContainerConfig({
62
+ SERVER_NAME: 'irc.test',
63
+ NETWORK_NAME: 'TestNet',
64
+ TCP_PORT: '7000',
65
+ });
66
+ expect(cfg.tcpPort).toBe(7000);
67
+ });
68
+
69
+ it('reads TCP_HOST override', () => {
70
+ const cfg = loadContainerConfig({
71
+ SERVER_NAME: 'irc.test',
72
+ NETWORK_NAME: 'TestNet',
73
+ TCP_HOST: '127.0.0.1',
74
+ });
75
+ expect(cfg.tcpHost).toBe('127.0.0.1');
76
+ });
77
+
78
+ it('persistencePath is undefined when PERSISTENCE_PATH is unset', () => {
79
+ const cfg = loadContainerConfig({ SERVER_NAME: 'irc.test', NETWORK_NAME: 'TestNet' });
80
+ expect(cfg.persistencePath).toBeUndefined();
81
+ });
82
+
83
+ it('reads PERSISTENCE_PATH', () => {
84
+ const cfg = loadContainerConfig({
85
+ SERVER_NAME: 'irc.test',
86
+ NETWORK_NAME: 'TestNet',
87
+ PERSISTENCE_PATH: '/data/state.json',
88
+ });
89
+ expect(cfg.persistencePath).toBe('/data/state.json');
90
+ });
91
+
92
+ it('defaults snapshotIntervalMs to 60_000', () => {
93
+ const cfg = loadContainerConfig({ SERVER_NAME: 'irc.test', NETWORK_NAME: 'TestNet' });
94
+ expect(cfg.snapshotIntervalMs).toBe(60_000);
95
+ });
96
+
97
+ it('reads SNAPSHOT_INTERVAL_MS override', () => {
98
+ const cfg = loadContainerConfig({
99
+ SERVER_NAME: 'irc.test',
100
+ NETWORK_NAME: 'TestNet',
101
+ SNAPSHOT_INTERVAL_MS: '5000',
102
+ });
103
+ expect(cfg.snapshotIntervalMs).toBe(5000);
104
+ });
105
+
106
+ it('passes through SERVER_VERSION, CREATED_AT, CHANNEL_PREFIXES, and QUIT_MESSAGE', () => {
107
+ const cfg = loadContainerConfig({
108
+ SERVER_NAME: 'irc.test',
109
+ NETWORK_NAME: 'TestNet',
110
+ SERVER_VERSION: '2.0.0',
111
+ CREATED_AT: '1700000000000',
112
+ CHANNEL_PREFIXES: '#&',
113
+ QUIT_MESSAGE: 'Bye',
114
+ });
115
+ expect(cfg.server.serverVersion).toBe('2.0.0');
116
+ expect(cfg.server.createdAt).toBe(1_700_000_000_000);
117
+ expect(cfg.server.channelPrefixes).toBe('#&');
118
+ expect(cfg.server.quitMessage).toBe('Bye');
119
+ });
120
+
121
+ it('passes CREATED_AT string verbatim when non-numeric', () => {
122
+ const cfg = loadContainerConfig({
123
+ SERVER_NAME: 'irc.test',
124
+ NETWORK_NAME: 'TestNet',
125
+ CREATED_AT: 'a sunny day',
126
+ });
127
+ expect(cfg.server.createdAt).toBe('a sunny day');
128
+ });
129
+
130
+ it('coerces MAX_CLIENTS, MAX_TARGETS_PER_COMMAND, CHANNEL_LEN, TOPIC_LEN, MAX_LIST_ENTRIES', () => {
131
+ const cfg = loadContainerConfig({
132
+ SERVER_NAME: 'irc.test',
133
+ NETWORK_NAME: 'TestNet',
134
+ MAX_CLIENTS: '500',
135
+ MAX_TARGETS_PER_COMMAND: '3',
136
+ CHANNEL_LEN: '64',
137
+ TOPIC_LEN: '500',
138
+ MAX_LIST_ENTRIES: '10',
139
+ });
140
+ expect(cfg.server.maxClients).toBe(500);
141
+ expect(cfg.server.maxTargetsPerCommand).toBe(3);
142
+ expect(cfg.server.channelLen).toBe(64);
143
+ expect(cfg.server.topicLen).toBe(500);
144
+ expect(cfg.server.maxListEntries).toBe(10);
145
+ });
146
+
147
+ it('parses OPER_USER + OPER_PASSWORD into operCreds', () => {
148
+ const cfg = loadContainerConfig({
149
+ SERVER_NAME: 'irc.test',
150
+ NETWORK_NAME: 'TestNet',
151
+ OPER_USER: 'admin',
152
+ OPER_PASSWORD: 'secret',
153
+ });
154
+ expect(cfg.server.operCreds).toEqual([{ user: 'admin', password: 'secret' }]);
155
+ });
156
+
157
+ it('skips malformed SASL account lines', () => {
158
+ const cfg = loadContainerConfig({
159
+ SERVER_NAME: 'irc.test',
160
+ NETWORK_NAME: 'TestNet',
161
+ SASL_ACCOUNTS: 'alice:secret\nbadline\n \n:bobpass',
162
+ });
163
+ expect(cfg.server.saslAccounts).toEqual([{ username: 'alice', password: 'secret' }]);
164
+ });
165
+
166
+ it('defaults tcpPort when TCP_PORT is empty or invalid', () => {
167
+ const cfg1 = loadContainerConfig({
168
+ SERVER_NAME: 'irc.test',
169
+ NETWORK_NAME: 'TestNet',
170
+ TCP_PORT: '',
171
+ });
172
+ expect(cfg1.tcpPort).toBe(6667);
173
+ const cfg2 = loadContainerConfig({
174
+ SERVER_NAME: 'irc.test',
175
+ NETWORK_NAME: 'TestNet',
176
+ TCP_PORT: 'not-a-number',
177
+ });
178
+ expect(cfg2.tcpPort).toBe(6667);
179
+ });
180
+
181
+ it('defaults tcpHost when TCP_HOST is empty', () => {
182
+ const cfg = loadContainerConfig({
183
+ SERVER_NAME: 'irc.test',
184
+ NETWORK_NAME: 'TestNet',
185
+ TCP_HOST: ' ',
186
+ });
187
+ expect(cfg.tcpHost).toBe('0.0.0.0');
188
+ });
189
+
190
+ it('defaults snapshotIntervalMs when SNAPSHOT_INTERVAL_MS is invalid', () => {
191
+ const cfg = loadContainerConfig({
192
+ SERVER_NAME: 'irc.test',
193
+ NETWORK_NAME: 'TestNet',
194
+ SNAPSHOT_INTERVAL_MS: 'abc',
195
+ });
196
+ expect(cfg.snapshotIntervalMs).toBe(60_000);
197
+ });
198
+
199
+ it('disables persistence when PERSISTENCE_PATH is empty', () => {
200
+ const cfg = loadContainerConfig({
201
+ SERVER_NAME: 'irc.test',
202
+ NETWORK_NAME: 'TestNet',
203
+ PERSISTENCE_PATH: '',
204
+ });
205
+ expect(cfg.persistencePath).toBeUndefined();
206
+ });
207
+
208
+ it('omits motdLines when MOTD_LINES is empty', () => {
209
+ const cfg = loadContainerConfig({
210
+ SERVER_NAME: 'irc.test',
211
+ NETWORK_NAME: 'TestNet',
212
+ MOTD_LINES: '',
213
+ });
214
+ // Schema default applies; just verify it doesn't crash.
215
+ expect(cfg.server.serverName).toBe('irc.test');
216
+ });
217
+ });