serverless-ircd 0.5.0 → 0.7.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 +40 -3
- package/.github/workflows/deploy-aws.yml +1 -2
- package/.github/workflows/deploy-cf-tcp.yml +1 -5
- package/.github/workflows/deploy-cf.yml +1 -2
- package/.gitmodules +3 -0
- package/CHANGELOG.md +474 -0
- package/README.md +219 -22
- package/apps/aws-stack/README.md +33 -0
- package/apps/aws-stack/package.json +1 -1
- package/apps/aws-stack/src/aws-stack.ts +74 -9
- package/apps/aws-stack/tests/coverage-command.test.ts +155 -0
- package/apps/aws-stack/tests/stack.test.ts +2 -2
- package/apps/aws-stack/tests/synth-no-bundle.test.ts +129 -0
- package/apps/aws-stack/tests/vitest.setup.ts +18 -0
- package/apps/aws-stack/vitest.config.ts +3 -0
- package/apps/cf-tcp-container/package.json +1 -1
- package/apps/cf-tcp-container/src/config-loader.ts +6 -7
- package/apps/cf-tcp-container/src/container-server.ts +3 -0
- package/apps/cf-tcp-container/tests/config-loader.test.ts +10 -3
- package/apps/cf-tcp-container/wrangler.toml +1 -1
- package/apps/cf-worker/package.json +2 -1
- package/apps/cf-worker/src/origin-allowlist.ts +99 -0
- package/apps/cf-worker/src/worker.ts +89 -9
- package/apps/cf-worker/tests/fixtures/web-dist/app/index.html +18 -0
- package/apps/cf-worker/tests/fixtures/web-dist/index.html +18 -0
- package/apps/cf-worker/tests/origin-allowlist.test.ts +101 -0
- package/apps/cf-worker/tests/smoke.test.ts +28 -2
- package/apps/cf-worker/tests/ws-origin.test.ts +89 -0
- package/apps/cf-worker/vitest.config.ts +1 -1
- package/apps/cf-worker/wrangler.test.toml +23 -0
- package/apps/cf-worker/wrangler.toml +59 -3
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/config-loader.ts +6 -6
- package/apps/local-cli/src/main.ts +20 -0
- package/apps/local-cli/src/server.ts +9 -7
- package/apps/local-cli/tests/e2e.test.ts +112 -28
- package/apps/local-cli/tests/tcp.test.ts +71 -19
- package/apps/local-cli/tests/ws-subprotocol.test.ts +12 -7
- package/apps/web/landing/index.html +217 -0
- package/apps/web/package.json +28 -0
- package/apps/web/scripts/build.mjs +125 -0
- package/apps/web/src/build-env.ts +43 -0
- package/apps/web/src/config-schema.ts +138 -0
- package/apps/web/static/config.json +28 -0
- package/apps/web/static/config.prod.json +28 -0
- package/apps/web/static/config.staging.json +28 -0
- package/apps/web/tests/build-env.test.ts +63 -0
- package/apps/web/tests/build-smoke.test.ts +103 -0
- package/apps/web/tests/config-schema.test.ts +432 -0
- package/apps/web/tests/workspace.test.ts +12 -0
- package/apps/web/tsconfig.json +10 -0
- package/apps/web/upstream/.browserslistrc +3 -0
- package/apps/web/upstream/.editorconfig +9 -0
- package/apps/web/upstream/.eslintignore +3 -0
- package/apps/web/upstream/.eslintrc.js +90 -0
- package/apps/web/upstream/.github/FUNDING.yml +12 -0
- package/apps/web/upstream/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- package/apps/web/upstream/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/apps/web/upstream/.github/workflows/push.yml +26 -0
- package/apps/web/upstream/.github/workflows/release.yml +33 -0
- package/apps/web/upstream/.github/workflows/release_manual.yml +55 -0
- package/apps/web/upstream/.prettierrc.js +9 -0
- package/apps/web/upstream/.stylelintignore +5 -0
- package/apps/web/upstream/.stylelintrc.js +21 -0
- package/apps/web/upstream/LICENSE +201 -0
- package/apps/web/upstream/README.md +76 -0
- package/apps/web/upstream/babel.config.js +25 -0
- package/apps/web/upstream/crowdin.yml +3 -0
- package/apps/web/upstream/index.html +35 -0
- package/apps/web/upstream/jest.config.js +7 -0
- package/apps/web/upstream/jsdoc.json +22 -0
- package/apps/web/upstream/package.json +80 -0
- package/apps/web/upstream/scripts/pre-commit +43 -0
- package/apps/web/upstream/src/components/App.vue +516 -0
- package/apps/web/upstream/src/components/AppSettings.vue +587 -0
- package/apps/web/upstream/src/components/AutoComplete.vue +284 -0
- package/apps/web/upstream/src/components/Avatar.vue +105 -0
- package/apps/web/upstream/src/components/AwayStatusIndicator.vue +77 -0
- package/apps/web/upstream/src/components/BufferKey.vue +121 -0
- package/apps/web/upstream/src/components/BufferSettings.vue +97 -0
- package/apps/web/upstream/src/components/Captcha.vue +85 -0
- package/apps/web/upstream/src/components/ChannelBanlist.vue +96 -0
- package/apps/web/upstream/src/components/ChannelInfo.vue +159 -0
- package/apps/web/upstream/src/components/ChannelInvitelist.vue +276 -0
- package/apps/web/upstream/src/components/ChannelList.vue +345 -0
- package/apps/web/upstream/src/components/Container.vue +401 -0
- package/apps/web/upstream/src/components/ContainerHeader.vue +522 -0
- package/apps/web/upstream/src/components/ControlInput.vue +1016 -0
- package/apps/web/upstream/src/components/LoadingAnimation.vue +104 -0
- package/apps/web/upstream/src/components/MediaViewer.vue +199 -0
- package/apps/web/upstream/src/components/MessageInfo.vue +192 -0
- package/apps/web/upstream/src/components/MessageList.vue +1028 -0
- package/apps/web/upstream/src/components/MessageListAvatar.vue +20 -0
- package/apps/web/upstream/src/components/MessageListMessageCompact.vue +361 -0
- package/apps/web/upstream/src/components/MessageListMessageInline.vue +257 -0
- package/apps/web/upstream/src/components/MessageListMessageModern.vue +430 -0
- package/apps/web/upstream/src/components/NetworkSettings.vue +528 -0
- package/apps/web/upstream/src/components/Nicklist.vue +309 -0
- package/apps/web/upstream/src/components/NicklistUser.vue +181 -0
- package/apps/web/upstream/src/components/NotConnected.vue +271 -0
- package/apps/web/upstream/src/components/SelfUser.vue +283 -0
- package/apps/web/upstream/src/components/ServerSelector.vue +162 -0
- package/apps/web/upstream/src/components/ServerView.vue +126 -0
- package/apps/web/upstream/src/components/SettingsAdvanced.vue +232 -0
- package/apps/web/upstream/src/components/SettingsAliases.vue +121 -0
- package/apps/web/upstream/src/components/Sidebar.vue +429 -0
- package/apps/web/upstream/src/components/SidebarAboutBuffer.vue +288 -0
- package/apps/web/upstream/src/components/SidebarState.vue +145 -0
- package/apps/web/upstream/src/components/StartupError.vue +35 -0
- package/apps/web/upstream/src/components/StateBrowser.vue +420 -0
- package/apps/web/upstream/src/components/StateBrowserBuffer.vue +124 -0
- package/apps/web/upstream/src/components/StateBrowserNetwork.vue +823 -0
- package/apps/web/upstream/src/components/StateBrowserUsermenu.vue +162 -0
- package/apps/web/upstream/src/components/TypingStatusIndicator.vue +34 -0
- package/apps/web/upstream/src/components/TypingUsersList.vue +56 -0
- package/apps/web/upstream/src/components/UrlEmbed.vue +149 -0
- package/apps/web/upstream/src/components/UserBox.vue +728 -0
- package/apps/web/upstream/src/components/inputtools/Emoji.vue +67 -0
- package/apps/web/upstream/src/components/inputtools/TextStyle.vue +142 -0
- package/apps/web/upstream/src/components/startups/CommonLayout.vue +208 -0
- package/apps/web/upstream/src/components/startups/CustomServer.vue +495 -0
- package/apps/web/upstream/src/components/startups/KiwiBnc.vue +361 -0
- package/apps/web/upstream/src/components/startups/Personal.vue +317 -0
- package/apps/web/upstream/src/components/startups/Welcome.vue +544 -0
- package/apps/web/upstream/src/components/startups/ZncLogin.vue +229 -0
- package/apps/web/upstream/src/components/utils/InputConfirm.vue +68 -0
- package/apps/web/upstream/src/components/utils/InputPrompt.vue +118 -0
- package/apps/web/upstream/src/components/utils/InputText.vue +172 -0
- package/apps/web/upstream/src/components/utils/IrcInput.vue +579 -0
- package/apps/web/upstream/src/components/utils/PluginWrapper.vue +26 -0
- package/apps/web/upstream/src/components/utils/TabbedView.vue +149 -0
- package/apps/web/upstream/src/components/utils/TransitionExpand.vue +97 -0
- package/apps/web/upstream/src/helpers/Colours.js +128 -0
- package/apps/web/upstream/src/helpers/IrcdDiffs.js +26 -0
- package/apps/web/upstream/src/helpers/Md5.js +193 -0
- package/apps/web/upstream/src/helpers/Misc.js +401 -0
- package/apps/web/upstream/src/helpers/TextFormatting.js +237 -0
- package/apps/web/upstream/src/libs/AliasRewriter.js +157 -0
- package/apps/web/upstream/src/libs/AudioManager.js +60 -0
- package/apps/web/upstream/src/libs/BouncerMiddleware.js +247 -0
- package/apps/web/upstream/src/libs/BouncerProvider.js +551 -0
- package/apps/web/upstream/src/libs/ChathistoryMiddleware.js +153 -0
- package/apps/web/upstream/src/libs/ConfigLoader.js +94 -0
- package/apps/web/upstream/src/libs/EmojiProvider.js +46 -0
- package/apps/web/upstream/src/libs/GlobalApi.js +346 -0
- package/apps/web/upstream/src/libs/IPC.js +51 -0
- package/apps/web/upstream/src/libs/InputHandler.js +921 -0
- package/apps/web/upstream/src/libs/IrcClient.js +1506 -0
- package/apps/web/upstream/src/libs/Logger.js +71 -0
- package/apps/web/upstream/src/libs/Message.js +169 -0
- package/apps/web/upstream/src/libs/MessageFormatter.js +364 -0
- package/apps/web/upstream/src/libs/MessageParser.js +249 -0
- package/apps/web/upstream/src/libs/Notifications.js +78 -0
- package/apps/web/upstream/src/libs/ServerConnection.js +270 -0
- package/apps/web/upstream/src/libs/ServerSession.js +102 -0
- package/apps/web/upstream/src/libs/SoundBleep.js +20 -0
- package/apps/web/upstream/src/libs/StatePersistence.js +90 -0
- package/apps/web/upstream/src/libs/ThemeManager.js +128 -0
- package/apps/web/upstream/src/libs/TypingMiddleware.js +105 -0
- package/apps/web/upstream/src/libs/WindowTitle.js +63 -0
- package/apps/web/upstream/src/libs/batchedAdd.js +74 -0
- package/apps/web/upstream/src/libs/bufferTools.js +177 -0
- package/apps/web/upstream/src/libs/polyfill/Element.closest.js +18 -0
- package/apps/web/upstream/src/libs/renderers/Html.js +113 -0
- package/apps/web/upstream/src/libs/settingTools.js +31 -0
- package/apps/web/upstream/src/libs/state/BufferState.js +703 -0
- package/apps/web/upstream/src/libs/state/NetworkState.js +158 -0
- package/apps/web/upstream/src/libs/state/UserState.js +89 -0
- package/apps/web/upstream/src/libs/state/common.js +26 -0
- package/apps/web/upstream/src/libs/state.js +961 -0
- package/apps/web/upstream/src/libs/storage/Local.js +51 -0
- package/apps/web/upstream/src/main.js +566 -0
- package/apps/web/upstream/src/res/autocompleteCommands.js +31 -0
- package/apps/web/upstream/src/res/configTemplates.js +373 -0
- package/apps/web/upstream/src/res/globalStyle.css +277 -0
- package/apps/web/upstream/src/res/kiwiLoadingLogo.png +0 -0
- package/apps/web/upstream/src/res/locales/app.af-ZA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ar-SA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.bg-BG.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.bs-BA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ca-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.cs-CZ.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.da-DK.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.de-DE.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.dev.po +1246 -0
- package/apps/web/upstream/src/res/locales/app.el-GR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.en-US.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-419.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-AR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-EM.po +536 -0
- package/apps/web/upstream/src/res/locales/app.es-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-US.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.eu-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.fi-FI.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.fr-FR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.gl-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.he-IL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.hi-IN.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.hu-HU.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.id-ID.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.it-IT.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ja-JP.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ko-KR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.nl-NL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.no-NO.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.pl-PL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.pt-BR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.pt-PT.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ro-RO.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ru-RU.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sl-SI.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sq-AL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sr-SP.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sv-SE.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.tr-TR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.uk-UA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.vi-VN.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.zh-CN.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.zh-TW.po +1145 -0
- package/apps/web/upstream/src/res/localesList.json +27 -0
- package/apps/web/upstream/src/res/logo.png +0 -0
- package/apps/web/upstream/src/thirdparty/about.html +43 -0
- package/apps/web/upstream/src/thirdparty/index.js +5 -0
- package/apps/web/upstream/src/thirdparty/kiwiirccom.vue +185 -0
- package/apps/web/upstream/static/config.json +28 -0
- package/apps/web/upstream/static/emoticons/smile.png +0 -0
- package/apps/web/upstream/static/favicon.png +0 -0
- package/apps/web/upstream/static/highlight.mp3 +0 -0
- package/apps/web/upstream/static/highlight.ogg +0 -0
- package/apps/web/upstream/static/locales/.gitignore +2 -0
- package/apps/web/upstream/static/plugins/customise.html.example +10 -0
- package/apps/web/upstream/static/themes/coffee/theme.css +145 -0
- package/apps/web/upstream/static/themes/common/base.css +1064 -0
- package/apps/web/upstream/static/themes/dark/theme.css +196 -0
- package/apps/web/upstream/static/themes/default/theme.css +92 -0
- package/apps/web/upstream/static/themes/elite/theme.css +248 -0
- package/apps/web/upstream/static/themes/grayfox/theme.css +143 -0
- package/apps/web/upstream/static/themes/nightswatch/theme.css +367 -0
- package/apps/web/upstream/static/themes/osprey/theme.css +115 -0
- package/apps/web/upstream/static/themes/radioactive/theme.css +1211 -0
- package/apps/web/upstream/static/themes/sky/theme.css +107 -0
- package/apps/web/upstream/tests/unit/BatchAdd.spec.js +174 -0
- package/apps/web/upstream/tests/unit/MessageParser.spec.js +125 -0
- package/apps/web/upstream/tests/unit/Misc.spec.js +24 -0
- package/apps/web/upstream/tests/unit/NetworkState.spec.js +58 -0
- package/apps/web/upstream/tests/unit/StartupError.spec.js +20 -0
- package/apps/web/upstream/vue.config.js +150 -0
- package/apps/web/upstream/yarn.lock +9767 -0
- package/apps/web/vitest.config.ts +13 -0
- package/biome.json +2 -0
- 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 +110 -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 +96 -0
- package/docs/ADR-007-deterministic-ports.md +82 -0
- package/docs/ADR-008-monorepo-tooling.md +60 -0
- package/docs/ADR-009-dual-transport-wss-and-irc-tls.md +138 -0
- package/docs/ADR-Index.md +15 -0
- package/docs/AWS-Adapter-Architecture.md +496 -0
- package/docs/AWS-Deployment.md +1275 -0
- package/docs/AWS-TCP-Deployment.md +410 -0
- package/docs/Cloudflare-Deployment-Guide.md +719 -0
- package/docs/Cloudflare-TCP-Deployment.md +510 -0
- package/docs/Home.md +16 -0
- package/docs/Observability.md +87 -0
- package/docs/PlanExtensions.md +812 -0
- package/docs/PlanIRCv3Websocket.md +503 -0
- package/docs/PlanWebClient.md +451 -0
- package/docs/Release-Process.md +443 -0
- package/docs/WebClientGuide.md +535 -0
- package/package.json +2 -2
- package/packages/aws-adapter/package.json +1 -1
- package/packages/aws-adapter/src/handlers/default.ts +3 -0
- package/packages/aws-adapter/src/handlers/nlb-stream.ts +9 -0
- package/packages/aws-adapter/src/serialize.ts +29 -1
- package/packages/aws-adapter/tests/account-store-dynamo.test.ts +49 -8
- package/packages/aws-adapter/tests/account-store.test.ts +4 -7
- package/packages/aws-adapter/tests/aws-harness.ts +5 -2
- package/packages/aws-adapter/tests/aws-integration.test.ts +8 -4
- package/packages/aws-adapter/tests/aws-runtime.test.ts +79 -0
- package/packages/aws-adapter/tests/config-loader.test.ts +11 -2
- package/packages/aws-adapter/tests/connect.test.ts +9 -9
- package/packages/aws-adapter/tests/disconnect-fanout.test.ts +3 -4
- package/packages/aws-adapter/tests/global-setup.ts +55 -30
- package/packages/aws-adapter/tests/gone-exception.test.ts +4 -9
- package/packages/aws-adapter/tests/handlers.test.ts +90 -4
- package/packages/aws-adapter/tests/message-store.test.ts +6 -5
- package/packages/aws-adapter/tests/nlb-stream.test.ts +60 -8
- package/packages/cf-adapter/package.json +2 -1
- package/packages/cf-adapter/src/connection-do.ts +3 -0
- package/packages/cf-adapter/src/serialize.ts +25 -4
- package/packages/cf-adapter/tests/cf-harness.ts +12 -1
- package/packages/cf-adapter/tests/cf-runtime.test.ts +269 -0
- package/packages/cf-adapter/tests/channel-do.test.ts +117 -0
- package/packages/cf-adapter/tests/config-loader.test.ts +49 -0
- package/packages/cf-adapter/tests/connection-do-pure.test.ts +91 -0
- package/packages/cf-adapter/tests/d1-account-store.test.ts +48 -0
- package/packages/cf-adapter/tests/registry-do.test.ts +104 -0
- package/packages/cf-adapter/tests/serialize.test.ts +308 -0
- package/packages/cf-adapter/vitest.config.ts +1 -1
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/irc-core/package.json +1 -1
- package/packages/irc-core/reports/mutation/mutation.html +342 -0
- package/packages/irc-core/src/caps/capabilities.ts +37 -3
- package/packages/irc-core/src/caps/index.ts +1 -0
- package/packages/irc-core/src/caps/sts.ts +84 -0
- package/packages/irc-core/src/commands/account-notify.ts +53 -0
- package/packages/irc-core/src/commands/away.ts +9 -3
- package/packages/irc-core/src/commands/cap.ts +23 -5
- package/packages/irc-core/src/commands/chathistory.ts +40 -11
- package/packages/irc-core/src/commands/index.ts +34 -0
- package/packages/irc-core/src/commands/isupport.ts +53 -0
- package/packages/irc-core/src/commands/mode.ts +8 -1
- package/packages/irc-core/src/commands/monitor.ts +327 -0
- package/packages/irc-core/src/commands/multiline.ts +256 -0
- package/packages/irc-core/src/commands/pre-away.ts +112 -0
- package/packages/irc-core/src/commands/privmsg.ts +19 -12
- package/packages/irc-core/src/commands/read-marker.ts +108 -0
- package/packages/irc-core/src/commands/registration.ts +8 -0
- package/packages/irc-core/src/commands/sasl.ts +24 -1
- package/packages/irc-core/src/commands/tagmsg.ts +71 -1
- package/packages/irc-core/src/commands/whois.ts +5 -0
- package/packages/irc-core/src/config.ts +65 -10
- package/packages/irc-core/src/effects.ts +14 -0
- package/packages/irc-core/src/ports.ts +389 -0
- package/packages/irc-core/src/protocol/index.ts +14 -0
- package/packages/irc-core/src/protocol/labeled-response.ts +133 -0
- package/packages/irc-core/src/protocol/numerics.ts +15 -0
- package/packages/irc-core/src/protocol/outbound.ts +16 -1
- package/packages/irc-core/src/protocol/standard-replies.ts +277 -0
- package/packages/irc-core/src/state/connection.ts +32 -1
- package/packages/irc-core/src/types.ts +112 -0
- package/packages/irc-core/stryker.commands.conf.json +1 -2
- package/packages/irc-core/tests/away-store.test.ts +73 -0
- package/packages/irc-core/tests/batch.test.ts +15 -0
- package/packages/irc-core/tests/caps/capabilities.test.ts +119 -2
- package/packages/irc-core/tests/caps/sts.test.ts +123 -0
- package/packages/irc-core/tests/commands/account-notify.test.ts +171 -0
- package/packages/irc-core/tests/commands/away.test.ts +85 -2
- package/packages/irc-core/tests/commands/cap.test.ts +165 -0
- package/packages/irc-core/tests/commands/chathistory.test.ts +148 -1
- package/packages/irc-core/tests/commands/echo-message.test.ts +9 -3
- package/packages/irc-core/tests/commands/isupport.test.ts +80 -0
- package/packages/irc-core/tests/commands/mode.test.ts +72 -0
- package/packages/irc-core/tests/commands/monitor.test.ts +411 -0
- package/packages/irc-core/tests/commands/multiline.test.ts +428 -0
- package/packages/irc-core/tests/commands/pre-away.test.ts +241 -0
- package/packages/irc-core/tests/commands/privmsg.test.ts +154 -3
- package/packages/irc-core/tests/commands/read-marker.test.ts +211 -0
- package/packages/irc-core/tests/commands/registration.test.ts +64 -0
- package/packages/irc-core/tests/commands/sasl.test.ts +116 -2
- package/packages/irc-core/tests/commands/tagmsg.test.ts +357 -0
- package/packages/irc-core/tests/commands/whois.test.ts +34 -0
- package/packages/irc-core/tests/config.test.ts +103 -13
- package/packages/irc-core/tests/labeled-response.test.ts +181 -0
- package/packages/irc-core/tests/message-tags.test.ts +114 -0
- package/packages/irc-core/tests/monitor-store.test.ts +200 -0
- package/packages/irc-core/tests/numerics.test.ts +13 -0
- package/packages/irc-core/tests/parser.test.ts +109 -0
- package/packages/irc-core/tests/read-marker-store.test.ts +108 -0
- package/packages/irc-core/tests/standard-replies.test.ts +412 -0
- package/packages/irc-core/tests/types.test.ts +27 -0
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +450 -3
- package/packages/irc-server/src/dispatch.ts +108 -16
- package/packages/irc-server/tests/actor.test.ts +1432 -39
- package/packages/irc-server/tests/dispatch.test.ts +263 -2
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/index.ts +6 -0
- package/packages/irc-test-support/src/scenarios.ts +24 -2
- package/packages/irc-test-support/src/test-config.ts +54 -0
- package/packages/irc-test-support/tests/test-config.test.ts +51 -0
- package/tools/ci-hardening/package.json +1 -1
- package/tools/ci-hardening/tests/ddb-local.test.ts +82 -0
- package/tools/load-test/package.json +33 -0
- package/tools/load-test/src/client.ts +351 -0
- package/tools/load-test/src/config.ts +313 -0
- package/tools/load-test/src/harness.ts +116 -0
- package/tools/load-test/src/main.ts +120 -0
- package/tools/load-test/src/metrics.ts +168 -0
- package/tools/load-test/src/report.ts +106 -0
- package/tools/load-test/tests/client.test.ts +212 -0
- package/tools/load-test/tests/config.test.ts +152 -0
- package/tools/load-test/tests/framing.test.ts +37 -0
- package/tools/load-test/tests/harness.test.ts +165 -0
- package/tools/load-test/tests/metrics.test.ts +174 -0
- package/tools/load-test/tests/report.test.ts +161 -0
- package/tools/load-test/tests/smoke.test.ts +67 -0
- package/tools/load-test/tsconfig.build.json +12 -0
- package/tools/load-test/tsconfig.test.json +10 -0
- package/tools/load-test/vitest.config.ts +29 -0
- package/tools/tcp-ws-forwarder/package.json +1 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { InMemoryReadMarkerStore, type ReadMarkerStore } from '../src/ports';
|
|
3
|
+
|
|
4
|
+
describe('InMemoryReadMarkerStore — port conformance', () => {
|
|
5
|
+
it('implements the ReadMarkerStore port', () => {
|
|
6
|
+
const store: ReadMarkerStore = new InMemoryReadMarkerStore();
|
|
7
|
+
expect(store).toBeDefined();
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('InMemoryReadMarkerStore — set + get', () => {
|
|
12
|
+
it('returns undefined for an unknown (account, channel) pair', () => {
|
|
13
|
+
const store = new InMemoryReadMarkerStore();
|
|
14
|
+
expect(store.get('alice', '#foo')).toBeUndefined();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('stores and returns the msgid for a known (account, channel) pair', () => {
|
|
18
|
+
const store = new InMemoryReadMarkerStore();
|
|
19
|
+
store.set('alice', '#foo', 'm1');
|
|
20
|
+
expect(store.get('alice', '#foo')).toBe('m1');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('overwrites the marker on a subsequent set for the same pair', () => {
|
|
24
|
+
const store = new InMemoryReadMarkerStore();
|
|
25
|
+
store.set('alice', '#foo', 'm1');
|
|
26
|
+
store.set('alice', '#foo', 'm2');
|
|
27
|
+
expect(store.get('alice', '#foo')).toBe('m2');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('keys channels case-insensitively (rfc1459 fold)', () => {
|
|
31
|
+
const store = new InMemoryReadMarkerStore();
|
|
32
|
+
store.set('alice', '#Foo', 'm1');
|
|
33
|
+
expect(store.get('alice', '#foo')).toBe('m1');
|
|
34
|
+
expect(store.get('alice', '#FOO')).toBe('m1');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('keys accounts case-insensitively (rfc1459 fold)', () => {
|
|
38
|
+
const store = new InMemoryReadMarkerStore();
|
|
39
|
+
store.set('Alice', '#foo', 'm1');
|
|
40
|
+
expect(store.get('alice', '#foo')).toBe('m1');
|
|
41
|
+
expect(store.get('ALICE', '#foo')).toBe('m1');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('keeps markers for distinct accounts on the same channel separate', () => {
|
|
45
|
+
const store = new InMemoryReadMarkerStore();
|
|
46
|
+
store.set('alice', '#foo', 'm1');
|
|
47
|
+
store.set('bob', '#foo', 'm9');
|
|
48
|
+
expect(store.get('alice', '#foo')).toBe('m1');
|
|
49
|
+
expect(store.get('bob', '#foo')).toBe('m9');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('keeps markers for distinct channels on the same account separate', () => {
|
|
53
|
+
const store = new InMemoryReadMarkerStore();
|
|
54
|
+
store.set('alice', '#foo', 'm1');
|
|
55
|
+
store.set('alice', '#bar', 'm2');
|
|
56
|
+
expect(store.get('alice', '#foo')).toBe('m1');
|
|
57
|
+
expect(store.get('alice', '#bar')).toBe('m2');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('InMemoryReadMarkerStore — forAccount', () => {
|
|
62
|
+
it('returns an empty array for an account with no markers', () => {
|
|
63
|
+
const store = new InMemoryReadMarkerStore();
|
|
64
|
+
expect(store.forAccount('alice')).toEqual([]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('returns every (channel, msgid) pair recorded for the account', () => {
|
|
68
|
+
const store = new InMemoryReadMarkerStore();
|
|
69
|
+
store.set('alice', '#foo', 'm1');
|
|
70
|
+
store.set('alice', '#bar', 'm2');
|
|
71
|
+
|
|
72
|
+
const entries = store.forAccount('alice');
|
|
73
|
+
expect(entries).toHaveLength(2);
|
|
74
|
+
const byChan = Object.fromEntries(entries.map((e) => [e.channel, e.msgid]));
|
|
75
|
+
expect(byChan['#foo']).toBe('m1');
|
|
76
|
+
expect(byChan['#bar']).toBe('m2');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('reports the account name on each entry', () => {
|
|
80
|
+
const store = new InMemoryReadMarkerStore();
|
|
81
|
+
store.set('alice', '#foo', 'm1');
|
|
82
|
+
const entries = store.forAccount('alice');
|
|
83
|
+
expect(entries[0]?.account).toBe('alice');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('reflects the most recent set when a pair is overwritten', () => {
|
|
87
|
+
const store = new InMemoryReadMarkerStore();
|
|
88
|
+
store.set('alice', '#foo', 'm1');
|
|
89
|
+
store.set('alice', '#foo', 'm2');
|
|
90
|
+
const entries = store.forAccount('alice');
|
|
91
|
+
expect(entries).toHaveLength(1);
|
|
92
|
+
expect(entries[0]?.msgid).toBe('m2');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('does not leak markers belonging to a different account', () => {
|
|
96
|
+
const store = new InMemoryReadMarkerStore();
|
|
97
|
+
store.set('alice', '#foo', 'm1');
|
|
98
|
+
store.set('bob', '#bar', 'm2');
|
|
99
|
+
expect(store.forAccount('alice')).toHaveLength(1);
|
|
100
|
+
expect(store.forAccount('bob')).toHaveLength(1);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('folds the account name case-insensitively on lookup', () => {
|
|
104
|
+
const store = new InMemoryReadMarkerStore();
|
|
105
|
+
store.set('Alice', '#foo', 'm1');
|
|
106
|
+
expect(store.forAccount('ALICE')).toHaveLength(1);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import type { RawLine } from '../src/effects';
|
|
3
|
+
import { Numerics } from '../src/protocol/numerics';
|
|
4
|
+
import { numericToStandardReply, toStandardReply } from '../src/protocol/standard-replies';
|
|
5
|
+
|
|
6
|
+
const L = (text: string): RawLine => ({ text });
|
|
7
|
+
|
|
8
|
+
const CAPS = new Set<string>(['standard-replies']);
|
|
9
|
+
const NO_CAPS = new Set<string>();
|
|
10
|
+
|
|
11
|
+
const SERVER = 'irc.example.com';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Builds a numeric line in the canonical `:server CODE nick [middle] :trailing`
|
|
15
|
+
* form this server emits.
|
|
16
|
+
*/
|
|
17
|
+
function numericLine(
|
|
18
|
+
code: number,
|
|
19
|
+
nick: string,
|
|
20
|
+
middle: string | undefined,
|
|
21
|
+
trailing: string,
|
|
22
|
+
): RawLine {
|
|
23
|
+
const codeStr = code.toString().padStart(3, '0');
|
|
24
|
+
const parts = [`:${SERVER}`, codeStr, nick];
|
|
25
|
+
if (middle !== undefined) parts.push(middle);
|
|
26
|
+
parts.push(`:${trailing}`);
|
|
27
|
+
return L(parts.join(' '));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// toStandardReply — 461 ERR_NEEDMOREPARAMS (the seed case)
|
|
32
|
+
// ============================================================================
|
|
33
|
+
|
|
34
|
+
describe('toStandardReply — 461 ERR_NEEDMOREPARAMS', () => {
|
|
35
|
+
it('rewrites 461 to FAIL <command> INVALID_PARAMS :description for a cap-enabled client', () => {
|
|
36
|
+
// Arrange: the canonical 461 form emitted by every reducer that ships a
|
|
37
|
+
// command name in the middle slot. The cap-enabled client should see
|
|
38
|
+
// the structured FAIL form instead.
|
|
39
|
+
const input = numericLine(
|
|
40
|
+
Numerics.ERR_NEEDMOREPARAMS,
|
|
41
|
+
'alice',
|
|
42
|
+
'JOIN',
|
|
43
|
+
'Not enough parameters',
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// Act
|
|
47
|
+
const out = toStandardReply(input, CAPS);
|
|
48
|
+
|
|
49
|
+
// Assert: middle (JOIN) becomes the FAIL COMMAND; the trailing text is
|
|
50
|
+
// preserved verbatim as the description. The nick token is dropped —
|
|
51
|
+
// standard replies are addressed by routing, not by the nick field.
|
|
52
|
+
expect(out.text).toBe(`:${SERVER} FAIL JOIN INVALID_PARAMS :Not enough parameters`);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// toStandardReply — 403 ERR_NOSUCHCHANNEL
|
|
58
|
+
// ============================================================================
|
|
59
|
+
|
|
60
|
+
describe('toStandardReply — 403 ERR_NOSUCHCHANNEL', () => {
|
|
61
|
+
it('rewrites 403 to FAIL JOIN NO_SUCH_CHANNEL <channel> :description', () => {
|
|
62
|
+
const input = numericLine(
|
|
63
|
+
Numerics.ERR_NOSUCHCHANNEL,
|
|
64
|
+
'alice',
|
|
65
|
+
'#nonexistent',
|
|
66
|
+
'No such channel',
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const out = toStandardReply(input, CAPS);
|
|
70
|
+
|
|
71
|
+
// The numeric's middle (the bad channel name) is emitted as the
|
|
72
|
+
// standard-reply context, and the reducer's command (best known as
|
|
73
|
+
// JOIN here — the most common caller) is the FAIL COMMAND.
|
|
74
|
+
expect(out.text).toBe(`:${SERVER} FAIL JOIN NO_SUCH_CHANNEL #nonexistent :No such channel`);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// toStandardReply — 432 ERR_ERRONEUSNICKNAME
|
|
80
|
+
// ============================================================================
|
|
81
|
+
|
|
82
|
+
describe('toStandardReply — 432 ERR_ERRONEUSNICKNAME', () => {
|
|
83
|
+
it('rewrites 432 to FAIL NICK INVALID_NICK <nick> :description', () => {
|
|
84
|
+
const input = numericLine(
|
|
85
|
+
Numerics.ERR_ERRONEUSNICKNAME,
|
|
86
|
+
'alice',
|
|
87
|
+
'b@dnick',
|
|
88
|
+
'Erroneous nickname',
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const out = toStandardReply(input, CAPS);
|
|
92
|
+
|
|
93
|
+
expect(out.text).toBe(`:${SERVER} FAIL NICK INVALID_NICK b@dnick :Erroneous nickname`);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// toStandardReply — 433 ERR_NICKNAMEINUSE
|
|
99
|
+
// ============================================================================
|
|
100
|
+
|
|
101
|
+
describe('toStandardReply — 433 ERR_NICKNAMEINUSE', () => {
|
|
102
|
+
it('rewrites 433 to FAIL NICK NICKNAME_IN_USE <nick> :description', () => {
|
|
103
|
+
const input = numericLine(
|
|
104
|
+
Numerics.ERR_NICKNAMEINUSE,
|
|
105
|
+
'alice',
|
|
106
|
+
'bob',
|
|
107
|
+
'Nickname is already in use',
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const out = toStandardReply(input, CAPS);
|
|
111
|
+
|
|
112
|
+
expect(out.text).toBe(`:${SERVER} FAIL NICK NICKNAME_IN_USE bob :Nickname is already in use`);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// ============================================================================
|
|
117
|
+
// toStandardReply — 904 ERR_SASLFAIL (SASL failure)
|
|
118
|
+
// ============================================================================
|
|
119
|
+
|
|
120
|
+
describe('toStandardReply — 904 ERR_SASLFAIL', () => {
|
|
121
|
+
it('rewrites 904 to FAIL AUTHENTICATE SASL_FAILED :description', () => {
|
|
122
|
+
const input = numericLine(
|
|
123
|
+
Numerics.ERR_SASLFAIL,
|
|
124
|
+
'alice',
|
|
125
|
+
undefined,
|
|
126
|
+
'SASL authentication failed',
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const out = toStandardReply(input, CAPS);
|
|
130
|
+
|
|
131
|
+
// 904 has no middle parameter; the structured form carries only the
|
|
132
|
+
// fixed AUTHENTICATE command and the SASL_FAILED code.
|
|
133
|
+
expect(out.text).toBe(`:${SERVER} FAIL AUTHENTICATE SASL_FAILED :SASL authentication failed`);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// ============================================================================
|
|
138
|
+
// toStandardReply — legacy client gating (no standard-replies cap)
|
|
139
|
+
// ============================================================================
|
|
140
|
+
|
|
141
|
+
describe('toStandardReply — legacy client gating', () => {
|
|
142
|
+
it('returns the unchanged 461 numeric when the recipient lacks the cap', () => {
|
|
143
|
+
const input = numericLine(
|
|
144
|
+
Numerics.ERR_NEEDMOREPARAMS,
|
|
145
|
+
'alice',
|
|
146
|
+
'JOIN',
|
|
147
|
+
'Not enough parameters',
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// Same line, same object identity — the helper short-circuits before
|
|
151
|
+
// touching the table when the cap is absent.
|
|
152
|
+
const out = toStandardReply(input, NO_CAPS);
|
|
153
|
+
|
|
154
|
+
expect(out).toBe(input);
|
|
155
|
+
expect(out.text).toBe(`:${SERVER} 461 alice JOIN :Not enough parameters`);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('returns the unchanged 403 numeric when the recipient lacks the cap', () => {
|
|
159
|
+
const input = numericLine(Numerics.ERR_NOSUCHCHANNEL, 'alice', '#nope', 'No such channel');
|
|
160
|
+
|
|
161
|
+
const out = toStandardReply(input, NO_CAPS);
|
|
162
|
+
|
|
163
|
+
expect(out).toBe(input);
|
|
164
|
+
expect(out.text).toBe(`:${SERVER} 403 alice #nope :No such channel`);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('returns the unchanged 433 numeric when the recipient lacks the cap', () => {
|
|
168
|
+
const input = numericLine(
|
|
169
|
+
Numerics.ERR_NICKNAMEINUSE,
|
|
170
|
+
'alice',
|
|
171
|
+
'bob',
|
|
172
|
+
'Nickname is already in use',
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const out = toStandardReply(input, NO_CAPS);
|
|
176
|
+
|
|
177
|
+
expect(out).toBe(input);
|
|
178
|
+
expect(out.text).toBe(`:${SERVER} 433 alice bob :Nickname is already in use`);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('returns the unchanged 904 numeric when the recipient lacks the cap', () => {
|
|
182
|
+
const input = numericLine(
|
|
183
|
+
Numerics.ERR_SASLFAIL,
|
|
184
|
+
'alice',
|
|
185
|
+
undefined,
|
|
186
|
+
'SASL authentication failed',
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const out = toStandardReply(input, NO_CAPS);
|
|
190
|
+
|
|
191
|
+
expect(out).toBe(input);
|
|
192
|
+
expect(out.text).toBe(`:${SERVER} 904 alice :SASL authentication failed`);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// ============================================================================
|
|
197
|
+
// toStandardReply — non-curated numerics pass through unchanged
|
|
198
|
+
// ============================================================================
|
|
199
|
+
|
|
200
|
+
describe('toStandardReply — non-curated numerics pass through', () => {
|
|
201
|
+
it('passes a 372 RPL_MOTD line through unchanged for a cap-enabled client', () => {
|
|
202
|
+
// Numeric-only families (MOTD lines, NAMES, the welcome block, …) are
|
|
203
|
+
// intentionally left on the numeric form per the spec.
|
|
204
|
+
const input = numericLine(Numerics.RPL_MOTD, 'alice', undefined, '- Welcome to ExampleNet');
|
|
205
|
+
|
|
206
|
+
const out = toStandardReply(input, CAPS);
|
|
207
|
+
|
|
208
|
+
expect(out).toBe(input);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('passes a 001 RPL_WELCOME line through unchanged for a cap-enabled client', () => {
|
|
212
|
+
const input = numericLine(Numerics.RPL_WELCOME, 'alice', undefined, 'Welcome to the network');
|
|
213
|
+
|
|
214
|
+
const out = toStandardReply(input, CAPS);
|
|
215
|
+
|
|
216
|
+
expect(out).toBe(input);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('passes a 353 RPL_NAMREPLY line through unchanged for a cap-enabled client', () => {
|
|
220
|
+
// NAMES replies carry two middle tokens (sigil + channel); they are
|
|
221
|
+
// not in the curated set and must be delivered verbatim.
|
|
222
|
+
const input = numericLine(Numerics.RPL_NAMREPLY, 'alice', '= #foo', 'alice bob @carol');
|
|
223
|
+
|
|
224
|
+
const out = toStandardReply(input, CAPS);
|
|
225
|
+
|
|
226
|
+
expect(out).toBe(input);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// ============================================================================
|
|
231
|
+
// toStandardReply — non-numeric lines pass through unchanged
|
|
232
|
+
// ============================================================================
|
|
233
|
+
|
|
234
|
+
describe('toStandardReply — non-numeric lines pass through', () => {
|
|
235
|
+
it('passes a PRIVMSG line through unchanged for a cap-enabled client', () => {
|
|
236
|
+
const input = L(':alice!alice@example.com PRIVMSG #foo :hi');
|
|
237
|
+
|
|
238
|
+
const out = toStandardReply(input, CAPS);
|
|
239
|
+
|
|
240
|
+
expect(out).toBe(input);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('passes a FAIL line already emitted by a reducer through unchanged', () => {
|
|
244
|
+
// Defensive: a reducer that already emits the structured form must not
|
|
245
|
+
// be re-translated. The leading token after the prefix is `FAIL`, not
|
|
246
|
+
// a three-digit numeric, so the parser rejects it.
|
|
247
|
+
const input = L(`:${SERVER} FAIL JOIN INVALID_PARAMS :Not enough parameters`);
|
|
248
|
+
|
|
249
|
+
const out = toStandardReply(input, CAPS);
|
|
250
|
+
|
|
251
|
+
expect(out).toBe(input);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('passes a line without a server prefix through unchanged', () => {
|
|
255
|
+
// e.g. a server-to-server PING carrying no leading `:`. Defensive.
|
|
256
|
+
const input = L('PING :ping.example.com');
|
|
257
|
+
|
|
258
|
+
const out = toStandardReply(input, CAPS);
|
|
259
|
+
|
|
260
|
+
expect(out).toBe(input);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// ============================================================================
|
|
265
|
+
// toStandardReply — defensive parsing (malformed numeric lines)
|
|
266
|
+
// ============================================================================
|
|
267
|
+
|
|
268
|
+
describe('toStandardReply — malformed numeric lines pass through', () => {
|
|
269
|
+
it('passes a bare server prefix through unchanged (no space, no code)', () => {
|
|
270
|
+
// `:irc.example.com` alone — every reducer emits at least one trailing
|
|
271
|
+
// token, so this never ships in practice; defensive.
|
|
272
|
+
const input = L(`:${SERVER}`);
|
|
273
|
+
|
|
274
|
+
const out = toStandardReply(input, CAPS);
|
|
275
|
+
|
|
276
|
+
expect(out).toBe(input);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('passes a server prefix with a single trailing token through unchanged', () => {
|
|
280
|
+
// `:irc.example.com 001` — no second space, so no nick and no trailing.
|
|
281
|
+
const input = L(`:${SERVER} 001`);
|
|
282
|
+
|
|
283
|
+
const out = toStandardReply(input, CAPS);
|
|
284
|
+
|
|
285
|
+
expect(out).toBe(input);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('passes a server prefix with a non-numeric command token through unchanged', () => {
|
|
289
|
+
// `:server PING …` — second token is not three digits, so this is not
|
|
290
|
+
// a numeric line; pass through verbatim.
|
|
291
|
+
const input = L(`:${SERVER} PING :ping`);
|
|
292
|
+
|
|
293
|
+
const out = toStandardReply(input, CAPS);
|
|
294
|
+
|
|
295
|
+
expect(out).toBe(input);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('passes a curated numeric line with no nick token through unchanged', () => {
|
|
299
|
+
// `:server 461 :trailing` — no nick between code and trailing. Every
|
|
300
|
+
// reducer emits the nick, so this never ships; defensive.
|
|
301
|
+
const input = L(`:${SERVER} 461 :Not enough parameters`);
|
|
302
|
+
|
|
303
|
+
const out = toStandardReply(input, CAPS);
|
|
304
|
+
|
|
305
|
+
expect(out).toBe(input);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('passes a numeric line with trailing whitespace after the code through unchanged', () => {
|
|
309
|
+
// `:server 001 ` — secondSpace is found but afterCode is empty. The
|
|
310
|
+
// reducer never emits this; defensive.
|
|
311
|
+
const input = L(`:${SERVER} 001 `);
|
|
312
|
+
|
|
313
|
+
const out = toStandardReply(input, CAPS);
|
|
314
|
+
|
|
315
|
+
expect(out).toBe(input);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('passes a numeric line whose head collapses to empty after trim through unchanged', () => {
|
|
319
|
+
// `:server 001 :trailing` — multiple spaces after the code, then the
|
|
320
|
+
// trailing param. The nick slot is empty after trimming; defensive.
|
|
321
|
+
const input = L(`:${SERVER} 001 :trailing`);
|
|
322
|
+
|
|
323
|
+
const out = toStandardReply(input, CAPS);
|
|
324
|
+
|
|
325
|
+
expect(out).toBe(input);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('passes a curated numeric line with no trailing parameter through unchanged', () => {
|
|
329
|
+
// `:server 461 alice JOIN` — no `:trailing`. The structured reply form
|
|
330
|
+
// requires a description, so the translator bails and the bare numeric
|
|
331
|
+
// ships verbatim (defensive).
|
|
332
|
+
const input = L(`:${SERVER} 461 alice JOIN`);
|
|
333
|
+
|
|
334
|
+
const out = toStandardReply(input, CAPS);
|
|
335
|
+
|
|
336
|
+
expect(out).toBe(input);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('passes a curated numeric line whose command-from-middle has no middle through unchanged', () => {
|
|
340
|
+
// 461 normally carries the failed command name as its middle param.
|
|
341
|
+
// When the middle is missing, the translator cannot infer the COMMAND
|
|
342
|
+
// token (required by the spec), so it bails and ships the numeric
|
|
343
|
+
// verbatim. Mirrors how some legacy daemons emit `461 nick :msg`.
|
|
344
|
+
const input = L(`:${SERVER} 461 alice :Not enough parameters`);
|
|
345
|
+
|
|
346
|
+
const out = toStandardReply(input, CAPS);
|
|
347
|
+
|
|
348
|
+
expect(out).toBe(input);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('passes a curated context-bearing numeric line with no middle through unchanged', () => {
|
|
352
|
+
// 403 normally carries the bad channel name as its middle param. When
|
|
353
|
+
// the middle is missing, no context token is available, so the FAIL
|
|
354
|
+
// form degrades to `FAIL JOIN NO_SUCH_CHANNEL :description` (no context).
|
|
355
|
+
const input = L(`:${SERVER} 403 alice :No such channel`);
|
|
356
|
+
|
|
357
|
+
const out = toStandardReply(input, CAPS);
|
|
358
|
+
|
|
359
|
+
// The structured form is still emitted; only the context slot is
|
|
360
|
+
// elided when the middle is missing.
|
|
361
|
+
expect(out.text).toBe(`:${SERVER} FAIL JOIN NO_SUCH_CHANNEL :No such channel`);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// ============================================================================
|
|
366
|
+
// numericToStandardReply — table data
|
|
367
|
+
// ============================================================================
|
|
368
|
+
|
|
369
|
+
describe('numericToStandardReply — curated table', () => {
|
|
370
|
+
it('maps every curated numeric from the ticket to a FAIL', () => {
|
|
371
|
+
const curated: ReadonlyArray<[number, string]> = [
|
|
372
|
+
[Numerics.ERR_NEEDMOREPARAMS, 'INVALID_PARAMS'],
|
|
373
|
+
[Numerics.ERR_NOSUCHCHANNEL, 'NO_SUCH_CHANNEL'],
|
|
374
|
+
[Numerics.ERR_ERRONEUSNICKNAME, 'INVALID_NICK'],
|
|
375
|
+
[Numerics.ERR_NICKNAMEINUSE, 'NICKNAME_IN_USE'],
|
|
376
|
+
[Numerics.ERR_SASLFAIL, 'SASL_FAILED'],
|
|
377
|
+
];
|
|
378
|
+
for (const [code, expected] of curated) {
|
|
379
|
+
const mapping = numericToStandardReply.get(code);
|
|
380
|
+
expect(mapping).toBeDefined();
|
|
381
|
+
expect(mapping?.kind).toBe('FAIL');
|
|
382
|
+
expect(mapping?.code).toBe(expected);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('leaves a non-curated error numeric unmapped', () => {
|
|
387
|
+
expect(numericToStandardReply.has(Numerics.ERR_NOSUCHNICK)).toBe(false);
|
|
388
|
+
expect(numericToStandardReply.has(Numerics.RPL_WELCOME)).toBe(false);
|
|
389
|
+
expect(numericToStandardReply.has(Numerics.ERR_PASSWDMISMATCH)).toBe(false);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('declares a command strategy for every mapping (no ambiguous rows)', () => {
|
|
393
|
+
// Invariant: every row resolves a COMMAND. When commandFromMiddle is
|
|
394
|
+
// false, a fixed `command` MUST be supplied — otherwise the helper
|
|
395
|
+
// would have to bail at runtime.
|
|
396
|
+
for (const mapping of numericToStandardReply.values()) {
|
|
397
|
+
if (mapping.commandFromMiddle) {
|
|
398
|
+
expect(mapping.contextFromMiddle).toBe(false);
|
|
399
|
+
} else {
|
|
400
|
+
expect(mapping.command).toBeDefined();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('never declares both commandFromMiddle and contextFromMiddle simultaneously', () => {
|
|
406
|
+
// The two strategies consume the same middle slot; they are mutually
|
|
407
|
+
// exclusive by construction.
|
|
408
|
+
for (const mapping of numericToStandardReply.values()) {
|
|
409
|
+
expect(mapping.commandFromMiddle && mapping.contextFromMiddle).toBe(false);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
});
|
|
@@ -131,6 +131,33 @@ describe('Ctx / Reducer types', () => {
|
|
|
131
131
|
const ctx = makeCtx();
|
|
132
132
|
expect(ctx.history).toBeUndefined();
|
|
133
133
|
});
|
|
134
|
+
|
|
135
|
+
it('threads an inbound label through buildCtx onto ctx.label', () => {
|
|
136
|
+
const ctx = buildCtx({
|
|
137
|
+
serverConfig: {
|
|
138
|
+
serverName: 'irc.example.com',
|
|
139
|
+
networkName: 'ExampleNet',
|
|
140
|
+
maxChannelsPerUser: 30,
|
|
141
|
+
maxTargetsPerCommand: 10,
|
|
142
|
+
maxListEntries: 50,
|
|
143
|
+
nickLen: 30,
|
|
144
|
+
channelLen: 50,
|
|
145
|
+
topicLen: 390,
|
|
146
|
+
quitMessage: 'Client Quit',
|
|
147
|
+
},
|
|
148
|
+
clock: new FakeClock(1_000),
|
|
149
|
+
ids: new SequentialIdFactory(),
|
|
150
|
+
motd: EmptyMotdProvider,
|
|
151
|
+
connection: createConnection({ id: 'c1', connectedSince: 0 }),
|
|
152
|
+
label: 'foo',
|
|
153
|
+
});
|
|
154
|
+
expect(ctx.label).toBe('foo');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('omits ctx.label when no label is supplied', () => {
|
|
158
|
+
const ctx = makeCtx();
|
|
159
|
+
expect(ctx.label).toBeUndefined();
|
|
160
|
+
});
|
|
134
161
|
});
|
|
135
162
|
|
|
136
163
|
describe('ServerConfig shape', () => {
|