@ai-devkit/channel-connector 0.13.3 → 0.14.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/README.md +1 -1
- package/dist/ChannelConfigRepository.d.ts +1 -1
- package/dist/ChannelConfigRepository.d.ts.map +1 -1
- package/dist/ChannelConfigRepository.js +13 -13
- package/dist/ChannelConfigRepository.js.map +1 -1
- package/dist/ChannelManager.d.ts +1 -1
- package/dist/ChannelManager.d.ts.map +1 -1
- package/dist/ChannelManager.js +9 -9
- package/dist/ChannelManager.js.map +1 -1
- package/dist/__tests__/ChannelConfigRepository.test.js +72 -72
- package/dist/__tests__/ChannelConfigRepository.test.js.map +1 -1
- package/dist/__tests__/ChannelManager.test.js +26 -26
- package/dist/__tests__/ChannelManager.test.js.map +1 -1
- package/dist/__tests__/adapters/slack/SlackAdapter.test.js +137 -137
- package/dist/__tests__/adapters/slack/SlackAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/slack/SlackMessageDelivery.test.js +36 -36
- package/dist/__tests__/adapters/slack/SlackMessageDelivery.test.js.map +1 -1
- package/dist/__tests__/adapters/slack/slackMarkdown.test.js +25 -25
- package/dist/__tests__/adapters/slack/slackMarkdown.test.js.map +1 -1
- package/dist/__tests__/adapters/slack/slackQuestion.test.js +41 -41
- package/dist/__tests__/adapters/slack/slackQuestion.test.js.map +1 -1
- package/dist/__tests__/adapters/telegram/TelegramAdapter.test.js +177 -154
- package/dist/__tests__/adapters/telegram/TelegramAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/telegram/telegramHtml.test.js +109 -109
- package/dist/__tests__/adapters/telegram/telegramHtml.test.js.map +1 -1
- package/dist/adapters/ChannelAdapter.d.ts +1 -1
- package/dist/adapters/ChannelAdapter.d.ts.map +1 -1
- package/dist/adapters/ChannelAdapter.js +1 -1
- package/dist/adapters/ChannelAdapter.js.map +1 -1
- package/dist/adapters/slack/SlackAdapter.d.ts +2 -2
- package/dist/adapters/slack/SlackAdapter.d.ts.map +1 -1
- package/dist/adapters/slack/SlackAdapter.js +17 -17
- package/dist/adapters/slack/SlackAdapter.js.map +1 -1
- package/dist/adapters/slack/SlackMessageDelivery.d.ts +1 -1
- package/dist/adapters/slack/SlackMessageDelivery.d.ts.map +1 -1
- package/dist/adapters/slack/SlackMessageDelivery.js +6 -6
- package/dist/adapters/slack/SlackMessageDelivery.js.map +1 -1
- package/dist/adapters/slack/slackMarkdown.d.ts.map +1 -1
- package/dist/adapters/slack/slackMarkdown.js +36 -36
- package/dist/adapters/slack/slackMarkdown.js.map +1 -1
- package/dist/adapters/slack/slackQuestion.d.ts +7 -7
- package/dist/adapters/slack/slackQuestion.d.ts.map +1 -1
- package/dist/adapters/slack/slackQuestion.js +13 -13
- package/dist/adapters/slack/slackQuestion.js.map +1 -1
- package/dist/adapters/telegram/TelegramAdapter.d.ts +12 -2
- package/dist/adapters/telegram/TelegramAdapter.d.ts.map +1 -1
- package/dist/adapters/telegram/TelegramAdapter.js +38 -27
- package/dist/adapters/telegram/TelegramAdapter.js.map +1 -1
- package/dist/adapters/telegram/TelegramMessageDelivery.d.ts.map +1 -1
- package/dist/adapters/telegram/TelegramMessageDelivery.js +2 -2
- package/dist/adapters/telegram/TelegramMessageDelivery.js.map +1 -1
- package/dist/adapters/telegram/telegramHtml.d.ts.map +1 -1
- package/dist/adapters/telegram/telegramHtml.js +16 -16
- package/dist/adapters/telegram/telegramHtml.js.map +1 -1
- package/dist/adapters/telegram/telegramKeyboard.d.ts +1 -1
- package/dist/adapters/telegram/telegramKeyboard.d.ts.map +1 -1
- package/dist/adapters/telegram/telegramKeyboard.js.map +1 -1
- package/dist/adapters/telegram/telegramMarkdown.d.ts.map +1 -1
- package/dist/adapters/telegram/telegramMarkdown.js +28 -28
- package/dist/adapters/telegram/telegramMarkdown.js.map +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +6 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +21 -22
- package/vitest.config.ts +16 -16
package/README.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChannelConfigRepository.d.ts","sourceRoot":"","sources":["../src/ChannelConfigRepository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK9D;;;;GAIG;AACH,qBAAa,uBAAuB;
|
|
1
|
+
{"version":3,"file":"ChannelConfigRepository.d.ts","sourceRoot":"","sources":["../src/ChannelConfigRepository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK9D;;;;GAIG;AACH,qBAAa,uBAAuB;IAClC,OAAO,CAAC,UAAU,CAAS;gBAEf,UAAU,CAAC,EAAE,MAAM;IAI/B;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,aAAa,CAAC;IASzC;;OAEG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAMnE;;OAEG;IACG,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMhD;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;YAKnD,WAAW;CAM1B"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as fs from
|
|
2
|
-
import * as path from
|
|
3
|
-
import * as os from
|
|
4
|
-
const DEFAULT_CONFIG_PATH = path.join(os.homedir(),
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
const DEFAULT_CONFIG_PATH = path.join(os.homedir(), ".ai-devkit", "channels.json");
|
|
5
5
|
const DEFAULT_CONFIG = {
|
|
6
6
|
channels: {}
|
|
7
7
|
};
|
|
@@ -15,10 +15,10 @@ const DEFAULT_CONFIG = {
|
|
|
15
15
|
this.configPath = configPath ?? DEFAULT_CONFIG_PATH;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
* Read the full config. Returns default empty config if file is missing or corrupt.
|
|
19
|
+
*/ async getConfig() {
|
|
20
20
|
try {
|
|
21
|
-
const raw = fs.readFileSync(this.configPath,
|
|
21
|
+
const raw = fs.readFileSync(this.configPath, "utf-8");
|
|
22
22
|
return JSON.parse(raw);
|
|
23
23
|
} catch {
|
|
24
24
|
return {
|
|
@@ -28,22 +28,22 @@ const DEFAULT_CONFIG = {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
* Save a channel entry. Creates the file and parent directory if needed.
|
|
32
|
+
*/ async saveChannel(name, entry) {
|
|
33
33
|
const config = await this.getConfig();
|
|
34
34
|
config.channels[name] = entry;
|
|
35
35
|
await this.writeConfig(config);
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
* Remove a channel entry by name.
|
|
39
|
+
*/ async removeChannel(name) {
|
|
40
40
|
const config = await this.getConfig();
|
|
41
41
|
delete config.channels[name];
|
|
42
42
|
await this.writeConfig(config);
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
* Get a single channel entry by name.
|
|
46
|
+
*/ async getChannel(name) {
|
|
47
47
|
const config = await this.getConfig();
|
|
48
48
|
return config.channels[name];
|
|
49
49
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ChannelConfigRepository.ts"],"sourcesContent":["import * as fs from
|
|
1
|
+
{"version":3,"sources":["../src/ChannelConfigRepository.ts"],"sourcesContent":["import * as fs from \"fs\";\nimport * as path from \"path\";\nimport * as os from \"os\";\nimport type { ChannelConfig, ChannelEntry } from \"./types.js\";\n\nconst DEFAULT_CONFIG_PATH = path.join(os.homedir(), \".ai-devkit\", \"channels.json\");\nconst DEFAULT_CONFIG: ChannelConfig = { channels: {} };\n\n/**\n * Persists channel configurations to disk.\n * Default location: ~/.ai-devkit/channels.json\n * File permissions are set to 0600 to protect tokens.\n */\nexport class ChannelConfigRepository {\n private configPath: string;\n\n constructor(configPath?: string) {\n this.configPath = configPath ?? DEFAULT_CONFIG_PATH;\n }\n\n /**\n * Read the full config. Returns default empty config if file is missing or corrupt.\n */\n async getConfig(): Promise<ChannelConfig> {\n try {\n const raw = fs.readFileSync(this.configPath, \"utf-8\");\n return JSON.parse(raw) as ChannelConfig;\n } catch {\n return { ...DEFAULT_CONFIG, channels: {} };\n }\n }\n\n /**\n * Save a channel entry. Creates the file and parent directory if needed.\n */\n async saveChannel(name: string, entry: ChannelEntry): Promise<void> {\n const config = await this.getConfig();\n config.channels[name] = entry;\n await this.writeConfig(config);\n }\n\n /**\n * Remove a channel entry by name.\n */\n async removeChannel(name: string): Promise<void> {\n const config = await this.getConfig();\n delete config.channels[name];\n await this.writeConfig(config);\n }\n\n /**\n * Get a single channel entry by name.\n */\n async getChannel(name: string): Promise<ChannelEntry | undefined> {\n const config = await this.getConfig();\n return config.channels[name];\n }\n\n private async writeConfig(config: ChannelConfig): Promise<void> {\n const dir = path.dirname(this.configPath);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), { mode: 0o600 });\n fs.chmodSync(this.configPath, 0o600);\n }\n}\n"],"names":["fs","path","os","DEFAULT_CONFIG_PATH","join","homedir","DEFAULT_CONFIG","channels","ChannelConfigRepository","configPath","getConfig","raw","readFileSync","JSON","parse","saveChannel","name","entry","config","writeConfig","removeChannel","getChannel","dir","dirname","mkdirSync","recursive","writeFileSync","stringify","mode","chmodSync"],"mappings":"AAAA,YAAYA,QAAQ,KAAK;AACzB,YAAYC,UAAU,OAAO;AAC7B,YAAYC,QAAQ,KAAK;AAGzB,MAAMC,sBAAsBF,KAAKG,IAAI,CAACF,GAAGG,OAAO,IAAI,cAAc;AAClE,MAAMC,iBAAgC;IAAEC,UAAU,CAAC;AAAE;AAErD;;;;CAIC,GACD,OAAO,MAAMC;IACHC,WAAmB;IAE3B,YAAYA,UAAmB,CAAE;QAC/B,IAAI,CAACA,UAAU,GAAGA,cAAcN;IAClC;IAEA;;GAEC,GACD,MAAMO,YAAoC;QACxC,IAAI;YACF,MAAMC,MAAMX,GAAGY,YAAY,CAAC,IAAI,CAACH,UAAU,EAAE;YAC7C,OAAOI,KAAKC,KAAK,CAACH;QACpB,EAAE,OAAM;YACN,OAAO;gBAAE,GAAGL,cAAc;gBAAEC,UAAU,CAAC;YAAE;QAC3C;IACF;IAEA;;GAEC,GACD,MAAMQ,YAAYC,IAAY,EAAEC,KAAmB,EAAiB;QAClE,MAAMC,SAAS,MAAM,IAAI,CAACR,SAAS;QACnCQ,OAAOX,QAAQ,CAACS,KAAK,GAAGC;QACxB,MAAM,IAAI,CAACE,WAAW,CAACD;IACzB;IAEA;;GAEC,GACD,MAAME,cAAcJ,IAAY,EAAiB;QAC/C,MAAME,SAAS,MAAM,IAAI,CAACR,SAAS;QACnC,OAAOQ,OAAOX,QAAQ,CAACS,KAAK;QAC5B,MAAM,IAAI,CAACG,WAAW,CAACD;IACzB;IAEA;;GAEC,GACD,MAAMG,WAAWL,IAAY,EAAqC;QAChE,MAAME,SAAS,MAAM,IAAI,CAACR,SAAS;QACnC,OAAOQ,OAAOX,QAAQ,CAACS,KAAK;IAC9B;IAEA,MAAcG,YAAYD,MAAqB,EAAiB;QAC9D,MAAMI,MAAMrB,KAAKsB,OAAO,CAAC,IAAI,CAACd,UAAU;QACxCT,GAAGwB,SAAS,CAACF,KAAK;YAAEG,WAAW;QAAK;QACpCzB,GAAG0B,aAAa,CAAC,IAAI,CAACjB,UAAU,EAAEI,KAAKc,SAAS,CAACT,QAAQ,MAAM,IAAI;YAAEU,MAAM;QAAM;QACjF5B,GAAG6B,SAAS,CAAC,IAAI,CAACpB,UAAU,EAAE;IAChC;AACF"}
|
package/dist/ChannelManager.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChannelManager.d.ts","sourceRoot":"","sources":["../src/ChannelManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE;;;GAGG;AACH,qBAAa,cAAc;
|
|
1
|
+
{"version":3,"file":"ChannelManager.d.ts","sourceRoot":"","sources":["../src/ChannelManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAA0C;IAE1D;;;OAGG;IACH,eAAe,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAO9C;;OAEG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAIpD;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAK/B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAI/B"}
|
package/dist/ChannelManager.js
CHANGED
|
@@ -4,28 +4,28 @@
|
|
|
4
4
|
*/ export class ChannelManager {
|
|
5
5
|
adapters = new Map();
|
|
6
6
|
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
* Register a channel adapter.
|
|
8
|
+
* @throws If an adapter for the same type is already registered.
|
|
9
|
+
*/ registerAdapter(adapter) {
|
|
10
10
|
if (this.adapters.has(adapter.type)) {
|
|
11
11
|
throw new Error(`Adapter for type "${adapter.type}" is already registered`);
|
|
12
12
|
}
|
|
13
13
|
this.adapters.set(adapter.type, adapter);
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
* Get a registered adapter by type.
|
|
17
|
+
*/ getAdapter(type) {
|
|
18
18
|
return this.adapters.get(type);
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
* Start all registered adapters.
|
|
22
|
+
*/ async startAll() {
|
|
23
23
|
const startPromises = Array.from(this.adapters.values()).map((a)=>a.start());
|
|
24
24
|
await Promise.all(startPromises);
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
* Stop all registered adapters.
|
|
28
|
+
*/ async stopAll() {
|
|
29
29
|
const stopPromises = Array.from(this.adapters.values()).map((a)=>a.stop());
|
|
30
30
|
await Promise.all(stopPromises);
|
|
31
31
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ChannelManager.ts"],"sourcesContent":["import type { ChannelAdapter } from
|
|
1
|
+
{"version":3,"sources":["../src/ChannelManager.ts"],"sourcesContent":["import type { ChannelAdapter } from \"./adapters/ChannelAdapter.js\";\n\n/**\n * Central registry for channel adapters.\n * Manages adapter lifecycle (start/stop).\n */\nexport class ChannelManager {\n private adapters: Map<string, ChannelAdapter> = new Map();\n\n /**\n * Register a channel adapter.\n * @throws If an adapter for the same type is already registered.\n */\n registerAdapter(adapter: ChannelAdapter): void {\n if (this.adapters.has(adapter.type)) {\n throw new Error(`Adapter for type \"${adapter.type}\" is already registered`);\n }\n this.adapters.set(adapter.type, adapter);\n }\n\n /**\n * Get a registered adapter by type.\n */\n getAdapter(type: string): ChannelAdapter | undefined {\n return this.adapters.get(type);\n }\n\n /**\n * Start all registered adapters.\n */\n async startAll(): Promise<void> {\n const startPromises = Array.from(this.adapters.values()).map((a) => a.start());\n await Promise.all(startPromises);\n }\n\n /**\n * Stop all registered adapters.\n */\n async stopAll(): Promise<void> {\n const stopPromises = Array.from(this.adapters.values()).map((a) => a.stop());\n await Promise.all(stopPromises);\n }\n}\n"],"names":["ChannelManager","adapters","Map","registerAdapter","adapter","has","type","Error","set","getAdapter","get","startAll","startPromises","Array","from","values","map","a","start","Promise","all","stopAll","stopPromises","stop"],"mappings":"AAEA;;;CAGC,GACD,OAAO,MAAMA;IACHC,WAAwC,IAAIC,MAAM;IAE1D;;;GAGC,GACDC,gBAAgBC,OAAuB,EAAQ;QAC7C,IAAI,IAAI,CAACH,QAAQ,CAACI,GAAG,CAACD,QAAQE,IAAI,GAAG;YACnC,MAAM,IAAIC,MAAM,CAAC,kBAAkB,EAAEH,QAAQE,IAAI,CAAC,uBAAuB,CAAC;QAC5E;QACA,IAAI,CAACL,QAAQ,CAACO,GAAG,CAACJ,QAAQE,IAAI,EAAEF;IAClC;IAEA;;GAEC,GACDK,WAAWH,IAAY,EAA8B;QACnD,OAAO,IAAI,CAACL,QAAQ,CAACS,GAAG,CAACJ;IAC3B;IAEA;;GAEC,GACD,MAAMK,WAA0B;QAC9B,MAAMC,gBAAgBC,MAAMC,IAAI,CAAC,IAAI,CAACb,QAAQ,CAACc,MAAM,IAAIC,GAAG,CAAC,CAACC,IAAMA,EAAEC,KAAK;QAC3E,MAAMC,QAAQC,GAAG,CAACR;IACpB;IAEA;;GAEC,GACD,MAAMS,UAAyB;QAC7B,MAAMC,eAAeT,MAAMC,IAAI,CAAC,IAAI,CAACb,QAAQ,CAACc,MAAM,IAAIC,GAAG,CAAC,CAACC,IAAMA,EAAEM,IAAI;QACzE,MAAMJ,QAAQC,GAAG,CAACE;IACpB;AACF"}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import * as fs from
|
|
2
|
-
import * as path from
|
|
3
|
-
import * as os from
|
|
4
|
-
import { ChannelConfigRepository } from
|
|
5
|
-
import { isSlackEntry } from
|
|
6
|
-
describe(
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
import { ChannelConfigRepository } from "../ChannelConfigRepository.js";
|
|
5
|
+
import { isSlackEntry } from "../types.js";
|
|
6
|
+
describe("ChannelConfigRepository", ()=>{
|
|
7
7
|
let tmpDir;
|
|
8
8
|
let configPath;
|
|
9
9
|
let store;
|
|
10
10
|
beforeEach(()=>{
|
|
11
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(),
|
|
12
|
-
configPath = path.join(tmpDir,
|
|
11
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "channel-connector-test-"));
|
|
12
|
+
configPath = path.join(tmpDir, "channels.json");
|
|
13
13
|
store = new ChannelConfigRepository(configPath);
|
|
14
14
|
});
|
|
15
15
|
afterEach(()=>{
|
|
@@ -19,50 +19,50 @@ describe('ChannelConfigRepository', ()=>{
|
|
|
19
19
|
});
|
|
20
20
|
});
|
|
21
21
|
const sampleEntry = {
|
|
22
|
-
type:
|
|
22
|
+
type: "telegram",
|
|
23
23
|
enabled: true,
|
|
24
|
-
createdAt:
|
|
24
|
+
createdAt: "2026-04-11T00:00:00Z",
|
|
25
25
|
config: {
|
|
26
|
-
botToken:
|
|
27
|
-
botUsername:
|
|
26
|
+
botToken: "test-token-123",
|
|
27
|
+
botUsername: "test_bot"
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
30
|
const slackEntry = {
|
|
31
|
-
type:
|
|
31
|
+
type: "slack",
|
|
32
32
|
enabled: true,
|
|
33
|
-
createdAt:
|
|
33
|
+
createdAt: "2026-08-06T00:00:00Z",
|
|
34
34
|
config: {
|
|
35
|
-
appToken:
|
|
36
|
-
botToken:
|
|
37
|
-
appId:
|
|
38
|
-
botUserId:
|
|
39
|
-
workspaceId:
|
|
40
|
-
workspaceName:
|
|
41
|
-
transport:
|
|
42
|
-
audience:
|
|
35
|
+
appToken: "xapp-fake",
|
|
36
|
+
botToken: "xoxb-fake",
|
|
37
|
+
appId: "A123",
|
|
38
|
+
botUserId: "U-BOT",
|
|
39
|
+
workspaceId: "T123",
|
|
40
|
+
workspaceName: "Sandbox",
|
|
41
|
+
transport: "socket-mode",
|
|
42
|
+
audience: "dm"
|
|
43
43
|
}
|
|
44
44
|
};
|
|
45
|
-
describe(
|
|
46
|
-
it(
|
|
45
|
+
describe("constructor", ()=>{
|
|
46
|
+
it("should use default path when no configPath provided", async ()=>{
|
|
47
47
|
const defaultStore = new ChannelConfigRepository();
|
|
48
48
|
// Should not throw — just uses default path
|
|
49
49
|
const config = await defaultStore.getConfig();
|
|
50
50
|
expect(config).toBeDefined();
|
|
51
51
|
});
|
|
52
52
|
});
|
|
53
|
-
describe(
|
|
54
|
-
it(
|
|
53
|
+
describe("getConfig", ()=>{
|
|
54
|
+
it("narrows provider configuration from the channel type", ()=>{
|
|
55
55
|
expect(isSlackEntry(slackEntry)).toBe(true);
|
|
56
|
-
if (!isSlackEntry(slackEntry)) throw new Error(
|
|
57
|
-
expect(slackEntry.config.workspaceId).toBe(
|
|
56
|
+
if (!isSlackEntry(slackEntry)) throw new Error("expected Slack entry");
|
|
57
|
+
expect(slackEntry.config.workspaceId).toBe("T123");
|
|
58
58
|
});
|
|
59
|
-
it(
|
|
59
|
+
it("should return default empty config when file does not exist", async ()=>{
|
|
60
60
|
const config = await store.getConfig();
|
|
61
61
|
expect(config).toEqual({
|
|
62
62
|
channels: {}
|
|
63
63
|
});
|
|
64
64
|
});
|
|
65
|
-
it(
|
|
65
|
+
it("should return parsed config when file exists", async ()=>{
|
|
66
66
|
fs.writeFileSync(configPath, JSON.stringify({
|
|
67
67
|
channels: {
|
|
68
68
|
telegram: sampleEntry
|
|
@@ -71,102 +71,102 @@ describe('ChannelConfigRepository', ()=>{
|
|
|
71
71
|
const config = await store.getConfig();
|
|
72
72
|
expect(config.channels.telegram).toEqual(sampleEntry);
|
|
73
73
|
});
|
|
74
|
-
it(
|
|
75
|
-
fs.writeFileSync(configPath,
|
|
74
|
+
it("should handle corrupted JSON gracefully", async ()=>{
|
|
75
|
+
fs.writeFileSync(configPath, "not valid json{{{");
|
|
76
76
|
const config = await store.getConfig();
|
|
77
77
|
expect(config).toEqual({
|
|
78
78
|
channels: {}
|
|
79
79
|
});
|
|
80
80
|
});
|
|
81
81
|
});
|
|
82
|
-
describe(
|
|
83
|
-
it(
|
|
84
|
-
await store.saveChannel(
|
|
85
|
-
const raw = fs.readFileSync(configPath,
|
|
82
|
+
describe("saveChannel", ()=>{
|
|
83
|
+
it("should create config file with channel entry", async ()=>{
|
|
84
|
+
await store.saveChannel("telegram", sampleEntry);
|
|
85
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
86
86
|
const config = JSON.parse(raw);
|
|
87
87
|
expect(config.channels.telegram).toEqual(sampleEntry);
|
|
88
88
|
});
|
|
89
|
-
it(
|
|
90
|
-
const nestedPath = path.join(tmpDir,
|
|
89
|
+
it("should create parent directory if missing", async ()=>{
|
|
90
|
+
const nestedPath = path.join(tmpDir, "nested", "dir", "channels.json");
|
|
91
91
|
const nestedStore = new ChannelConfigRepository(nestedPath);
|
|
92
|
-
await nestedStore.saveChannel(
|
|
92
|
+
await nestedStore.saveChannel("telegram", sampleEntry);
|
|
93
93
|
expect(fs.existsSync(nestedPath)).toBe(true);
|
|
94
94
|
});
|
|
95
|
-
it(
|
|
96
|
-
await store.saveChannel(
|
|
95
|
+
it("should set file permissions to 0600", async ()=>{
|
|
96
|
+
await store.saveChannel("telegram", sampleEntry);
|
|
97
97
|
const stats = fs.statSync(configPath);
|
|
98
98
|
const mode = (stats.mode & 0o777).toString(8);
|
|
99
|
-
expect(mode).toBe(
|
|
99
|
+
expect(mode).toBe("600");
|
|
100
100
|
});
|
|
101
|
-
it(
|
|
101
|
+
it("should repair permissive permissions on an existing config file", async ()=>{
|
|
102
102
|
fs.writeFileSync(configPath, JSON.stringify({
|
|
103
103
|
channels: {}
|
|
104
104
|
}), {
|
|
105
105
|
mode: 0o644
|
|
106
106
|
});
|
|
107
107
|
fs.chmodSync(configPath, 0o644);
|
|
108
|
-
await store.saveChannel(
|
|
108
|
+
await store.saveChannel("slack", slackEntry);
|
|
109
109
|
expect(fs.statSync(configPath).mode & 0o777).toBe(0o600);
|
|
110
110
|
});
|
|
111
|
-
it(
|
|
112
|
-
await store.saveChannel(
|
|
113
|
-
await store.saveChannel(
|
|
111
|
+
it("should preserve existing channels when adding a new one", async ()=>{
|
|
112
|
+
await store.saveChannel("telegram", sampleEntry);
|
|
113
|
+
await store.saveChannel("slack", slackEntry);
|
|
114
114
|
const config = await store.getConfig();
|
|
115
115
|
expect(Object.keys(config.channels)).toEqual([
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
"telegram",
|
|
117
|
+
"slack"
|
|
118
118
|
]);
|
|
119
119
|
expect(config.channels.slack).toEqual(slackEntry);
|
|
120
120
|
});
|
|
121
|
-
it(
|
|
122
|
-
await store.saveChannel(
|
|
121
|
+
it("should preserve separate Telegram configs by channel name", async ()=>{
|
|
122
|
+
await store.saveChannel("personal", {
|
|
123
123
|
...sampleEntry,
|
|
124
124
|
config: {
|
|
125
|
-
botToken:
|
|
126
|
-
botUsername:
|
|
125
|
+
botToken: "personal-token",
|
|
126
|
+
botUsername: "personal_bot",
|
|
127
127
|
authorizedChatId: 111
|
|
128
128
|
}
|
|
129
129
|
});
|
|
130
|
-
await store.saveChannel(
|
|
130
|
+
await store.saveChannel("work", {
|
|
131
131
|
...sampleEntry,
|
|
132
132
|
config: {
|
|
133
|
-
botToken:
|
|
134
|
-
botUsername:
|
|
133
|
+
botToken: "work-token",
|
|
134
|
+
botUsername: "work_bot",
|
|
135
135
|
authorizedChatId: 222
|
|
136
136
|
}
|
|
137
137
|
});
|
|
138
138
|
const config = await store.getConfig();
|
|
139
139
|
expect(config.channels.personal.config).toEqual({
|
|
140
|
-
botToken:
|
|
141
|
-
botUsername:
|
|
140
|
+
botToken: "personal-token",
|
|
141
|
+
botUsername: "personal_bot",
|
|
142
142
|
authorizedChatId: 111
|
|
143
143
|
});
|
|
144
144
|
expect(config.channels.work.config).toEqual({
|
|
145
|
-
botToken:
|
|
146
|
-
botUsername:
|
|
145
|
+
botToken: "work-token",
|
|
146
|
+
botUsername: "work_bot",
|
|
147
147
|
authorizedChatId: 222
|
|
148
148
|
});
|
|
149
149
|
});
|
|
150
150
|
});
|
|
151
|
-
describe(
|
|
152
|
-
it(
|
|
153
|
-
await store.saveChannel(
|
|
154
|
-
await store.removeChannel(
|
|
151
|
+
describe("removeChannel", ()=>{
|
|
152
|
+
it("should remove a channel entry", async ()=>{
|
|
153
|
+
await store.saveChannel("telegram", sampleEntry);
|
|
154
|
+
await store.removeChannel("telegram");
|
|
155
155
|
const config = await store.getConfig();
|
|
156
156
|
expect(config.channels.telegram).toBeUndefined();
|
|
157
157
|
});
|
|
158
|
-
it(
|
|
159
|
-
await expect(store.removeChannel(
|
|
158
|
+
it("should not throw when removing non-existent channel", async ()=>{
|
|
159
|
+
await expect(store.removeChannel("nonexistent")).resolves.toBeUndefined();
|
|
160
160
|
});
|
|
161
161
|
});
|
|
162
|
-
describe(
|
|
163
|
-
it(
|
|
164
|
-
await store.saveChannel(
|
|
165
|
-
const entry = await store.getChannel(
|
|
162
|
+
describe("getChannel", ()=>{
|
|
163
|
+
it("should return the channel entry", async ()=>{
|
|
164
|
+
await store.saveChannel("telegram", sampleEntry);
|
|
165
|
+
const entry = await store.getChannel("telegram");
|
|
166
166
|
expect(entry).toEqual(sampleEntry);
|
|
167
167
|
});
|
|
168
|
-
it(
|
|
169
|
-
const entry = await store.getChannel(
|
|
168
|
+
it("should return undefined for non-existent channel", async ()=>{
|
|
169
|
+
const entry = await store.getChannel("slack");
|
|
170
170
|
expect(entry).toBeUndefined();
|
|
171
171
|
});
|
|
172
172
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/__tests__/ChannelConfigRepository.test.ts"],"sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport { ChannelConfigRepository } from '../ChannelConfigRepository.js';\nimport { isSlackEntry } from '../types.js';\nimport type { ChannelEntry } from '../types.js';\n\ndescribe('ChannelConfigRepository', () => {\n let tmpDir: string;\n let configPath: string;\n let store: ChannelConfigRepository;\n\n beforeEach(() => {\n tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'channel-connector-test-'));\n configPath = path.join(tmpDir, 'channels.json');\n store = new ChannelConfigRepository(configPath);\n });\n\n afterEach(() => {\n fs.rmSync(tmpDir, { recursive: true, force: true });\n });\n\n const sampleEntry: ChannelEntry = {\n type: 'telegram',\n enabled: true,\n createdAt: '2026-04-11T00:00:00Z',\n config: {\n botToken: 'test-token-123',\n botUsername: 'test_bot',\n },\n };\n\n const slackEntry: ChannelEntry = {\n type: 'slack',\n enabled: true,\n createdAt: '2026-08-06T00:00:00Z',\n config: {\n appToken: 'xapp-fake',\n botToken: 'xoxb-fake',\n appId: 'A123',\n botUserId: 'U-BOT',\n workspaceId: 'T123',\n workspaceName: 'Sandbox',\n transport: 'socket-mode',\n audience: 'dm',\n },\n };\n\n describe('constructor', () => {\n it('should use default path when no configPath provided', async () => {\n const defaultStore = new ChannelConfigRepository();\n // Should not throw — just uses default path\n const config = await defaultStore.getConfig();\n expect(config).toBeDefined();\n });\n });\n\n describe('getConfig', () => {\n it('narrows provider configuration from the channel type', () => {\n expect(isSlackEntry(slackEntry)).toBe(true);\n if (!isSlackEntry(slackEntry)) throw new Error('expected Slack entry');\n expect(slackEntry.config.workspaceId).toBe('T123');\n });\n\n it('should return default empty config when file does not exist', async () => {\n const config = await store.getConfig();\n expect(config).toEqual({ channels: {} });\n });\n\n it('should return parsed config when file exists', async () => {\n fs.writeFileSync(configPath, JSON.stringify({\n channels: { telegram: sampleEntry }\n }));\n\n const config = await store.getConfig();\n expect(config.channels.telegram).toEqual(sampleEntry);\n });\n\n it('should handle corrupted JSON gracefully', async () => {\n fs.writeFileSync(configPath, 'not valid json{{{');\n\n const config = await store.getConfig();\n expect(config).toEqual({ channels: {} });\n });\n });\n\n describe('saveChannel', () => {\n it('should create config file with channel entry', async () => {\n await store.saveChannel('telegram', sampleEntry);\n\n const raw = fs.readFileSync(configPath, 'utf-8');\n const config = JSON.parse(raw);\n expect(config.channels.telegram).toEqual(sampleEntry);\n });\n\n it('should create parent directory if missing', async () => {\n const nestedPath = path.join(tmpDir, 'nested', 'dir', 'channels.json');\n const nestedStore = new ChannelConfigRepository(nestedPath);\n\n await nestedStore.saveChannel('telegram', sampleEntry);\n\n expect(fs.existsSync(nestedPath)).toBe(true);\n });\n\n it('should set file permissions to 0600', async () => {\n await store.saveChannel('telegram', sampleEntry);\n\n const stats = fs.statSync(configPath);\n const mode = (stats.mode & 0o777).toString(8);\n expect(mode).toBe('600');\n });\n\n it('should repair permissive permissions on an existing config file', async () => {\n fs.writeFileSync(configPath, JSON.stringify({ channels: {} }), { mode: 0o644 });\n fs.chmodSync(configPath, 0o644);\n\n await store.saveChannel('slack', slackEntry);\n\n expect(fs.statSync(configPath).mode & 0o777).toBe(0o600);\n });\n\n it('should preserve existing channels when adding a new one', async () => {\n await store.saveChannel('telegram', sampleEntry);\n await store.saveChannel('slack', slackEntry);\n\n const config = await store.getConfig();\n expect(Object.keys(config.channels)).toEqual(['telegram', 'slack']);\n expect(config.channels.slack).toEqual(slackEntry);\n });\n\n it('should preserve separate Telegram configs by channel name', async () => {\n await store.saveChannel('personal', {\n ...sampleEntry,\n config: {\n botToken: 'personal-token',\n botUsername: 'personal_bot',\n authorizedChatId: 111,\n },\n });\n await store.saveChannel('work', {\n ...sampleEntry,\n config: {\n botToken: 'work-token',\n botUsername: 'work_bot',\n authorizedChatId: 222,\n },\n });\n\n const config = await store.getConfig();\n expect(config.channels.personal.config).toEqual({\n botToken: 'personal-token',\n botUsername: 'personal_bot',\n authorizedChatId: 111,\n });\n expect(config.channels.work.config).toEqual({\n botToken: 'work-token',\n botUsername: 'work_bot',\n authorizedChatId: 222,\n });\n });\n });\n\n describe('removeChannel', () => {\n it('should remove a channel entry', async () => {\n await store.saveChannel('telegram', sampleEntry);\n await store.removeChannel('telegram');\n\n const config = await store.getConfig();\n expect(config.channels.telegram).toBeUndefined();\n });\n\n it('should not throw when removing non-existent channel', async () => {\n await expect(store.removeChannel('nonexistent')).resolves.toBeUndefined();\n });\n });\n\n describe('getChannel', () => {\n it('should return the channel entry', async () => {\n await store.saveChannel('telegram', sampleEntry);\n\n const entry = await store.getChannel('telegram');\n expect(entry).toEqual(sampleEntry);\n });\n\n it('should return undefined for non-existent channel', async () => {\n const entry = await store.getChannel('slack');\n expect(entry).toBeUndefined();\n });\n });\n});\n"],"names":["fs","path","os","ChannelConfigRepository","isSlackEntry","describe","tmpDir","configPath","store","beforeEach","mkdtempSync","join","tmpdir","afterEach","rmSync","recursive","force","sampleEntry","type","enabled","createdAt","config","botToken","botUsername","slackEntry","appToken","appId","botUserId","workspaceId","workspaceName","transport","audience","it","defaultStore","getConfig","expect","toBeDefined","toBe","Error","toEqual","channels","writeFileSync","JSON","stringify","telegram","saveChannel","raw","readFileSync","parse","nestedPath","nestedStore","existsSync","stats","statSync","mode","toString","chmodSync","Object","keys","slack","authorizedChatId","personal","work","removeChannel","toBeUndefined","resolves","entry","getChannel"],"mappings":"AAAA,YAAYA,QAAQ,KAAK;AACzB,YAAYC,UAAU,OAAO;AAC7B,YAAYC,QAAQ,KAAK;AACzB,SAASC,uBAAuB,QAAQ,gCAAgC;AACxE,SAASC,YAAY,QAAQ,cAAc;AAG3CC,SAAS,2BAA2B;IAChC,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IAEJC,WAAW;QACPH,SAASN,GAAGU,WAAW,CAACT,KAAKU,IAAI,CAACT,GAAGU,MAAM,IAAI;QAC/CL,aAAaN,KAAKU,IAAI,CAACL,QAAQ;QAC/BE,QAAQ,IAAIL,wBAAwBI;IACxC;IAEAM,UAAU;QACNb,GAAGc,MAAM,CAACR,QAAQ;YAAES,WAAW;YAAMC,OAAO;QAAK;IACrD;IAEA,MAAMC,cAA4B;QAC9BC,MAAM;QACNC,SAAS;QACTC,WAAW;QACXC,QAAQ;YACJC,UAAU;YACVC,aAAa;QACjB;IACJ;IAEA,MAAMC,aAA2B;QAC7BN,MAAM;QACNC,SAAS;QACTC,WAAW;QACXC,QAAQ;YACJI,UAAU;YACVH,UAAU;YACVI,OAAO;YACPC,WAAW;YACXC,aAAa;YACbC,eAAe;YACfC,WAAW;YACXC,UAAU;QACd;IACJ;IAEA1B,SAAS,eAAe;QACpB2B,GAAG,uDAAuD;YACtD,MAAMC,eAAe,IAAI9B;YACzB,4CAA4C;YAC5C,MAAMkB,SAAS,MAAMY,aAAaC,SAAS;YAC3CC,OAAOd,QAAQe,WAAW;QAC9B;IACJ;IAEA/B,SAAS,aAAa;QAClB2B,GAAG,wDAAwD;YACvDG,OAAO/B,aAAaoB,aAAaa,IAAI,CAAC;YACtC,IAAI,CAACjC,aAAaoB,aAAa,MAAM,IAAIc,MAAM;YAC/CH,OAAOX,WAAWH,MAAM,CAACO,WAAW,EAAES,IAAI,CAAC;QAC/C;QAEAL,GAAG,+DAA+D;YAC9D,MAAMX,SAAS,MAAMb,MAAM0B,SAAS;YACpCC,OAAOd,QAAQkB,OAAO,CAAC;gBAAEC,UAAU,CAAC;YAAE;QAC1C;QAEAR,GAAG,gDAAgD;YAC/ChC,GAAGyC,aAAa,CAAClC,YAAYmC,KAAKC,SAAS,CAAC;gBACxCH,UAAU;oBAAEI,UAAU3B;gBAAY;YACtC;YAEA,MAAMI,SAAS,MAAMb,MAAM0B,SAAS;YACpCC,OAAOd,OAAOmB,QAAQ,CAACI,QAAQ,EAAEL,OAAO,CAACtB;QAC7C;QAEAe,GAAG,2CAA2C;YAC1ChC,GAAGyC,aAAa,CAAClC,YAAY;YAE7B,MAAMc,SAAS,MAAMb,MAAM0B,SAAS;YACpCC,OAAOd,QAAQkB,OAAO,CAAC;gBAAEC,UAAU,CAAC;YAAE;QAC1C;IACJ;IAEAnC,SAAS,eAAe;QACpB2B,GAAG,gDAAgD;YAC/C,MAAMxB,MAAMqC,WAAW,CAAC,YAAY5B;YAEpC,MAAM6B,MAAM9C,GAAG+C,YAAY,CAACxC,YAAY;YACxC,MAAMc,SAASqB,KAAKM,KAAK,CAACF;YAC1BX,OAAOd,OAAOmB,QAAQ,CAACI,QAAQ,EAAEL,OAAO,CAACtB;QAC7C;QAEAe,GAAG,6CAA6C;YAC5C,MAAMiB,aAAahD,KAAKU,IAAI,CAACL,QAAQ,UAAU,OAAO;YACtD,MAAM4C,cAAc,IAAI/C,wBAAwB8C;YAEhD,MAAMC,YAAYL,WAAW,CAAC,YAAY5B;YAE1CkB,OAAOnC,GAAGmD,UAAU,CAACF,aAAaZ,IAAI,CAAC;QAC3C;QAEAL,GAAG,uCAAuC;YACtC,MAAMxB,MAAMqC,WAAW,CAAC,YAAY5B;YAEpC,MAAMmC,QAAQpD,GAAGqD,QAAQ,CAAC9C;YAC1B,MAAM+C,OAAO,AAACF,CAAAA,MAAME,IAAI,GAAG,KAAI,EAAGC,QAAQ,CAAC;YAC3CpB,OAAOmB,MAAMjB,IAAI,CAAC;QACtB;QAEAL,GAAG,mEAAmE;YAClEhC,GAAGyC,aAAa,CAAClC,YAAYmC,KAAKC,SAAS,CAAC;gBAAEH,UAAU,CAAC;YAAE,IAAI;gBAAEc,MAAM;YAAM;YAC7EtD,GAAGwD,SAAS,CAACjD,YAAY;YAEzB,MAAMC,MAAMqC,WAAW,CAAC,SAASrB;YAEjCW,OAAOnC,GAAGqD,QAAQ,CAAC9C,YAAY+C,IAAI,GAAG,OAAOjB,IAAI,CAAC;QACtD;QAEAL,GAAG,2DAA2D;YAC1D,MAAMxB,MAAMqC,WAAW,CAAC,YAAY5B;YACpC,MAAMT,MAAMqC,WAAW,CAAC,SAASrB;YAEjC,MAAMH,SAAS,MAAMb,MAAM0B,SAAS;YACpCC,OAAOsB,OAAOC,IAAI,CAACrC,OAAOmB,QAAQ,GAAGD,OAAO,CAAC;gBAAC;gBAAY;aAAQ;YAClEJ,OAAOd,OAAOmB,QAAQ,CAACmB,KAAK,EAAEpB,OAAO,CAACf;QAC1C;QAEAQ,GAAG,6DAA6D;YAC5D,MAAMxB,MAAMqC,WAAW,CAAC,YAAY;gBAChC,GAAG5B,WAAW;gBACdI,QAAQ;oBACJC,UAAU;oBACVC,aAAa;oBACbqC,kBAAkB;gBACtB;YACJ;YACA,MAAMpD,MAAMqC,WAAW,CAAC,QAAQ;gBAC5B,GAAG5B,WAAW;gBACdI,QAAQ;oBACJC,UAAU;oBACVC,aAAa;oBACbqC,kBAAkB;gBACtB;YACJ;YAEA,MAAMvC,SAAS,MAAMb,MAAM0B,SAAS;YACpCC,OAAOd,OAAOmB,QAAQ,CAACqB,QAAQ,CAACxC,MAAM,EAAEkB,OAAO,CAAC;gBAC5CjB,UAAU;gBACVC,aAAa;gBACbqC,kBAAkB;YACtB;YACAzB,OAAOd,OAAOmB,QAAQ,CAACsB,IAAI,CAACzC,MAAM,EAAEkB,OAAO,CAAC;gBACxCjB,UAAU;gBACVC,aAAa;gBACbqC,kBAAkB;YACtB;QACJ;IACJ;IAEAvD,SAAS,iBAAiB;QACtB2B,GAAG,iCAAiC;YAChC,MAAMxB,MAAMqC,WAAW,CAAC,YAAY5B;YACpC,MAAMT,MAAMuD,aAAa,CAAC;YAE1B,MAAM1C,SAAS,MAAMb,MAAM0B,SAAS;YACpCC,OAAOd,OAAOmB,QAAQ,CAACI,QAAQ,EAAEoB,aAAa;QAClD;QAEAhC,GAAG,uDAAuD;YACtD,MAAMG,OAAO3B,MAAMuD,aAAa,CAAC,gBAAgBE,QAAQ,CAACD,aAAa;QAC3E;IACJ;IAEA3D,SAAS,cAAc;QACnB2B,GAAG,mCAAmC;YAClC,MAAMxB,MAAMqC,WAAW,CAAC,YAAY5B;YAEpC,MAAMiD,QAAQ,MAAM1D,MAAM2D,UAAU,CAAC;YACrChC,OAAO+B,OAAO3B,OAAO,CAACtB;QAC1B;QAEAe,GAAG,oDAAoD;YACnD,MAAMkC,QAAQ,MAAM1D,MAAM2D,UAAU,CAAC;YACrChC,OAAO+B,OAAOF,aAAa;QAC/B;IACJ;AACJ"}
|
|
1
|
+
{"version":3,"sources":["../../src/__tests__/ChannelConfigRepository.test.ts"],"sourcesContent":["import * as fs from \"fs\";\nimport * as path from \"path\";\nimport * as os from \"os\";\nimport { ChannelConfigRepository } from \"../ChannelConfigRepository.js\";\nimport { isSlackEntry } from \"../types.js\";\nimport type { ChannelEntry } from \"../types.js\";\n\ndescribe(\"ChannelConfigRepository\", () => {\n let tmpDir: string;\n let configPath: string;\n let store: ChannelConfigRepository;\n\n beforeEach(() => {\n tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), \"channel-connector-test-\"));\n configPath = path.join(tmpDir, \"channels.json\");\n store = new ChannelConfigRepository(configPath);\n });\n\n afterEach(() => {\n fs.rmSync(tmpDir, { recursive: true, force: true });\n });\n\n const sampleEntry: ChannelEntry = {\n type: \"telegram\",\n enabled: true,\n createdAt: \"2026-04-11T00:00:00Z\",\n config: {\n botToken: \"test-token-123\",\n botUsername: \"test_bot\",\n },\n };\n\n const slackEntry: ChannelEntry = {\n type: \"slack\",\n enabled: true,\n createdAt: \"2026-08-06T00:00:00Z\",\n config: {\n appToken: \"xapp-fake\",\n botToken: \"xoxb-fake\",\n appId: \"A123\",\n botUserId: \"U-BOT\",\n workspaceId: \"T123\",\n workspaceName: \"Sandbox\",\n transport: \"socket-mode\",\n audience: \"dm\",\n },\n };\n\n describe(\"constructor\", () => {\n it(\"should use default path when no configPath provided\", async () => {\n const defaultStore = new ChannelConfigRepository();\n // Should not throw — just uses default path\n const config = await defaultStore.getConfig();\n expect(config).toBeDefined();\n });\n });\n\n describe(\"getConfig\", () => {\n it(\"narrows provider configuration from the channel type\", () => {\n expect(isSlackEntry(slackEntry)).toBe(true);\n if (!isSlackEntry(slackEntry)) throw new Error(\"expected Slack entry\");\n expect(slackEntry.config.workspaceId).toBe(\"T123\");\n });\n\n it(\"should return default empty config when file does not exist\", async () => {\n const config = await store.getConfig();\n expect(config).toEqual({ channels: {} });\n });\n\n it(\"should return parsed config when file exists\", async () => {\n fs.writeFileSync(\n configPath,\n JSON.stringify({\n channels: { telegram: sampleEntry },\n }),\n );\n\n const config = await store.getConfig();\n expect(config.channels.telegram).toEqual(sampleEntry);\n });\n\n it(\"should handle corrupted JSON gracefully\", async () => {\n fs.writeFileSync(configPath, \"not valid json{{{\");\n\n const config = await store.getConfig();\n expect(config).toEqual({ channels: {} });\n });\n });\n\n describe(\"saveChannel\", () => {\n it(\"should create config file with channel entry\", async () => {\n await store.saveChannel(\"telegram\", sampleEntry);\n\n const raw = fs.readFileSync(configPath, \"utf-8\");\n const config = JSON.parse(raw);\n expect(config.channels.telegram).toEqual(sampleEntry);\n });\n\n it(\"should create parent directory if missing\", async () => {\n const nestedPath = path.join(tmpDir, \"nested\", \"dir\", \"channels.json\");\n const nestedStore = new ChannelConfigRepository(nestedPath);\n\n await nestedStore.saveChannel(\"telegram\", sampleEntry);\n\n expect(fs.existsSync(nestedPath)).toBe(true);\n });\n\n it(\"should set file permissions to 0600\", async () => {\n await store.saveChannel(\"telegram\", sampleEntry);\n\n const stats = fs.statSync(configPath);\n const mode = (stats.mode & 0o777).toString(8);\n expect(mode).toBe(\"600\");\n });\n\n it(\"should repair permissive permissions on an existing config file\", async () => {\n fs.writeFileSync(configPath, JSON.stringify({ channels: {} }), { mode: 0o644 });\n fs.chmodSync(configPath, 0o644);\n\n await store.saveChannel(\"slack\", slackEntry);\n\n expect(fs.statSync(configPath).mode & 0o777).toBe(0o600);\n });\n\n it(\"should preserve existing channels when adding a new one\", async () => {\n await store.saveChannel(\"telegram\", sampleEntry);\n await store.saveChannel(\"slack\", slackEntry);\n\n const config = await store.getConfig();\n expect(Object.keys(config.channels)).toEqual([\"telegram\", \"slack\"]);\n expect(config.channels.slack).toEqual(slackEntry);\n });\n\n it(\"should preserve separate Telegram configs by channel name\", async () => {\n await store.saveChannel(\"personal\", {\n ...sampleEntry,\n config: {\n botToken: \"personal-token\",\n botUsername: \"personal_bot\",\n authorizedChatId: 111,\n },\n });\n await store.saveChannel(\"work\", {\n ...sampleEntry,\n config: {\n botToken: \"work-token\",\n botUsername: \"work_bot\",\n authorizedChatId: 222,\n },\n });\n\n const config = await store.getConfig();\n expect(config.channels.personal.config).toEqual({\n botToken: \"personal-token\",\n botUsername: \"personal_bot\",\n authorizedChatId: 111,\n });\n expect(config.channels.work.config).toEqual({\n botToken: \"work-token\",\n botUsername: \"work_bot\",\n authorizedChatId: 222,\n });\n });\n });\n\n describe(\"removeChannel\", () => {\n it(\"should remove a channel entry\", async () => {\n await store.saveChannel(\"telegram\", sampleEntry);\n await store.removeChannel(\"telegram\");\n\n const config = await store.getConfig();\n expect(config.channels.telegram).toBeUndefined();\n });\n\n it(\"should not throw when removing non-existent channel\", async () => {\n await expect(store.removeChannel(\"nonexistent\")).resolves.toBeUndefined();\n });\n });\n\n describe(\"getChannel\", () => {\n it(\"should return the channel entry\", async () => {\n await store.saveChannel(\"telegram\", sampleEntry);\n\n const entry = await store.getChannel(\"telegram\");\n expect(entry).toEqual(sampleEntry);\n });\n\n it(\"should return undefined for non-existent channel\", async () => {\n const entry = await store.getChannel(\"slack\");\n expect(entry).toBeUndefined();\n });\n });\n});\n"],"names":["fs","path","os","ChannelConfigRepository","isSlackEntry","describe","tmpDir","configPath","store","beforeEach","mkdtempSync","join","tmpdir","afterEach","rmSync","recursive","force","sampleEntry","type","enabled","createdAt","config","botToken","botUsername","slackEntry","appToken","appId","botUserId","workspaceId","workspaceName","transport","audience","it","defaultStore","getConfig","expect","toBeDefined","toBe","Error","toEqual","channels","writeFileSync","JSON","stringify","telegram","saveChannel","raw","readFileSync","parse","nestedPath","nestedStore","existsSync","stats","statSync","mode","toString","chmodSync","Object","keys","slack","authorizedChatId","personal","work","removeChannel","toBeUndefined","resolves","entry","getChannel"],"mappings":"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"}
|
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
import { ChannelManager } from
|
|
1
|
+
import { ChannelManager } from "../ChannelManager.js";
|
|
2
2
|
function createMockAdapter(type) {
|
|
3
3
|
return {
|
|
4
4
|
type,
|
|
5
5
|
start: vi.fn().mockResolvedValue(undefined),
|
|
6
6
|
stop: vi.fn().mockResolvedValue(undefined),
|
|
7
7
|
sendMessage: vi.fn().mockResolvedValue({
|
|
8
|
-
messageId:
|
|
8
|
+
messageId: "1"
|
|
9
9
|
}),
|
|
10
10
|
onMessage: vi.fn(),
|
|
11
11
|
isHealthy: vi.fn().mockResolvedValue(true)
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
-
describe(
|
|
14
|
+
describe("ChannelManager", ()=>{
|
|
15
15
|
let manager;
|
|
16
16
|
beforeEach(()=>{
|
|
17
17
|
manager = new ChannelManager();
|
|
18
18
|
});
|
|
19
|
-
describe(
|
|
20
|
-
it(
|
|
21
|
-
const adapter = createMockAdapter(
|
|
19
|
+
describe("registerAdapter", ()=>{
|
|
20
|
+
it("should register an adapter", ()=>{
|
|
21
|
+
const adapter = createMockAdapter("telegram");
|
|
22
22
|
manager.registerAdapter(adapter);
|
|
23
|
-
expect(manager.getAdapter(
|
|
23
|
+
expect(manager.getAdapter("telegram")).toBe(adapter);
|
|
24
24
|
});
|
|
25
|
-
it(
|
|
26
|
-
const adapter1 = createMockAdapter(
|
|
27
|
-
const adapter2 = createMockAdapter(
|
|
25
|
+
it("should throw on duplicate adapter type", ()=>{
|
|
26
|
+
const adapter1 = createMockAdapter("telegram");
|
|
27
|
+
const adapter2 = createMockAdapter("telegram");
|
|
28
28
|
manager.registerAdapter(adapter1);
|
|
29
29
|
expect(()=>manager.registerAdapter(adapter2)).toThrow('Adapter for type "telegram" is already registered');
|
|
30
30
|
});
|
|
31
31
|
});
|
|
32
|
-
describe(
|
|
33
|
-
it(
|
|
34
|
-
expect(manager.getAdapter(
|
|
32
|
+
describe("getAdapter", ()=>{
|
|
33
|
+
it("should return undefined for unregistered type", ()=>{
|
|
34
|
+
expect(manager.getAdapter("slack")).toBeUndefined();
|
|
35
35
|
});
|
|
36
|
-
it(
|
|
37
|
-
const adapter = createMockAdapter(
|
|
36
|
+
it("should return the registered adapter", ()=>{
|
|
37
|
+
const adapter = createMockAdapter("telegram");
|
|
38
38
|
manager.registerAdapter(adapter);
|
|
39
|
-
expect(manager.getAdapter(
|
|
39
|
+
expect(manager.getAdapter("telegram")).toBe(adapter);
|
|
40
40
|
});
|
|
41
41
|
});
|
|
42
|
-
describe(
|
|
43
|
-
it(
|
|
44
|
-
const telegram = createMockAdapter(
|
|
45
|
-
const slack = createMockAdapter(
|
|
42
|
+
describe("startAll", ()=>{
|
|
43
|
+
it("should call start() on all registered adapters", async ()=>{
|
|
44
|
+
const telegram = createMockAdapter("telegram");
|
|
45
|
+
const slack = createMockAdapter("slack");
|
|
46
46
|
manager.registerAdapter(telegram);
|
|
47
47
|
manager.registerAdapter(slack);
|
|
48
48
|
await manager.startAll();
|
|
49
49
|
expect(telegram.start).toHaveBeenCalledTimes(1);
|
|
50
50
|
expect(slack.start).toHaveBeenCalledTimes(1);
|
|
51
51
|
});
|
|
52
|
-
it(
|
|
52
|
+
it("should work with no adapters", async ()=>{
|
|
53
53
|
await expect(manager.startAll()).resolves.toBeUndefined();
|
|
54
54
|
});
|
|
55
55
|
});
|
|
56
|
-
describe(
|
|
57
|
-
it(
|
|
58
|
-
const telegram = createMockAdapter(
|
|
59
|
-
const slack = createMockAdapter(
|
|
56
|
+
describe("stopAll", ()=>{
|
|
57
|
+
it("should call stop() on all registered adapters", async ()=>{
|
|
58
|
+
const telegram = createMockAdapter("telegram");
|
|
59
|
+
const slack = createMockAdapter("slack");
|
|
60
60
|
manager.registerAdapter(telegram);
|
|
61
61
|
manager.registerAdapter(slack);
|
|
62
62
|
await manager.stopAll();
|
|
63
63
|
expect(telegram.stop).toHaveBeenCalledTimes(1);
|
|
64
64
|
expect(slack.stop).toHaveBeenCalledTimes(1);
|
|
65
65
|
});
|
|
66
|
-
it(
|
|
66
|
+
it("should work with no adapters", async ()=>{
|
|
67
67
|
await expect(manager.stopAll()).resolves.toBeUndefined();
|
|
68
68
|
});
|
|
69
69
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/__tests__/ChannelManager.test.ts"],"sourcesContent":["import type { Mocked } from
|
|
1
|
+
{"version":3,"sources":["../../src/__tests__/ChannelManager.test.ts"],"sourcesContent":["import type { Mocked } from \"vitest\";\nimport { ChannelManager } from \"../ChannelManager.js\";\nimport type { ChannelAdapter } from \"../adapters/ChannelAdapter.js\";\n\nfunction createMockAdapter(type: string): Mocked<ChannelAdapter> {\n return {\n type,\n start: vi.fn().mockResolvedValue(undefined),\n stop: vi.fn().mockResolvedValue(undefined),\n sendMessage: vi.fn().mockResolvedValue({ messageId: \"1\" }),\n onMessage: vi.fn(),\n isHealthy: vi.fn().mockResolvedValue(true),\n };\n}\n\ndescribe(\"ChannelManager\", () => {\n let manager: ChannelManager;\n\n beforeEach(() => {\n manager = new ChannelManager();\n });\n\n describe(\"registerAdapter\", () => {\n it(\"should register an adapter\", () => {\n const adapter = createMockAdapter(\"telegram\");\n manager.registerAdapter(adapter);\n expect(manager.getAdapter(\"telegram\")).toBe(adapter);\n });\n\n it(\"should throw on duplicate adapter type\", () => {\n const adapter1 = createMockAdapter(\"telegram\");\n const adapter2 = createMockAdapter(\"telegram\");\n manager.registerAdapter(adapter1);\n expect(() => manager.registerAdapter(adapter2)).toThrow(\n 'Adapter for type \"telegram\" is already registered',\n );\n });\n });\n\n describe(\"getAdapter\", () => {\n it(\"should return undefined for unregistered type\", () => {\n expect(manager.getAdapter(\"slack\")).toBeUndefined();\n });\n\n it(\"should return the registered adapter\", () => {\n const adapter = createMockAdapter(\"telegram\");\n manager.registerAdapter(adapter);\n expect(manager.getAdapter(\"telegram\")).toBe(adapter);\n });\n });\n\n describe(\"startAll\", () => {\n it(\"should call start() on all registered adapters\", async () => {\n const telegram = createMockAdapter(\"telegram\");\n const slack = createMockAdapter(\"slack\");\n manager.registerAdapter(telegram);\n manager.registerAdapter(slack);\n\n await manager.startAll();\n\n expect(telegram.start).toHaveBeenCalledTimes(1);\n expect(slack.start).toHaveBeenCalledTimes(1);\n });\n\n it(\"should work with no adapters\", async () => {\n await expect(manager.startAll()).resolves.toBeUndefined();\n });\n });\n\n describe(\"stopAll\", () => {\n it(\"should call stop() on all registered adapters\", async () => {\n const telegram = createMockAdapter(\"telegram\");\n const slack = createMockAdapter(\"slack\");\n manager.registerAdapter(telegram);\n manager.registerAdapter(slack);\n\n await manager.stopAll();\n\n expect(telegram.stop).toHaveBeenCalledTimes(1);\n expect(slack.stop).toHaveBeenCalledTimes(1);\n });\n\n it(\"should work with no adapters\", async () => {\n await expect(manager.stopAll()).resolves.toBeUndefined();\n });\n });\n});\n"],"names":["ChannelManager","createMockAdapter","type","start","vi","fn","mockResolvedValue","undefined","stop","sendMessage","messageId","onMessage","isHealthy","describe","manager","beforeEach","it","adapter","registerAdapter","expect","getAdapter","toBe","adapter1","adapter2","toThrow","toBeUndefined","telegram","slack","startAll","toHaveBeenCalledTimes","resolves","stopAll"],"mappings":"AACA,SAASA,cAAc,QAAQ,uBAAuB;AAGtD,SAASC,kBAAkBC,IAAY;IACrC,OAAO;QACLA;QACAC,OAAOC,GAAGC,EAAE,GAAGC,iBAAiB,CAACC;QACjCC,MAAMJ,GAAGC,EAAE,GAAGC,iBAAiB,CAACC;QAChCE,aAAaL,GAAGC,EAAE,GAAGC,iBAAiB,CAAC;YAAEI,WAAW;QAAI;QACxDC,WAAWP,GAAGC,EAAE;QAChBO,WAAWR,GAAGC,EAAE,GAAGC,iBAAiB,CAAC;IACvC;AACF;AAEAO,SAAS,kBAAkB;IACzB,IAAIC;IAEJC,WAAW;QACTD,UAAU,IAAId;IAChB;IAEAa,SAAS,mBAAmB;QAC1BG,GAAG,8BAA8B;YAC/B,MAAMC,UAAUhB,kBAAkB;YAClCa,QAAQI,eAAe,CAACD;YACxBE,OAAOL,QAAQM,UAAU,CAAC,aAAaC,IAAI,CAACJ;QAC9C;QAEAD,GAAG,0CAA0C;YAC3C,MAAMM,WAAWrB,kBAAkB;YACnC,MAAMsB,WAAWtB,kBAAkB;YACnCa,QAAQI,eAAe,CAACI;YACxBH,OAAO,IAAML,QAAQI,eAAe,CAACK,WAAWC,OAAO,CACrD;QAEJ;IACF;IAEAX,SAAS,cAAc;QACrBG,GAAG,iDAAiD;YAClDG,OAAOL,QAAQM,UAAU,CAAC,UAAUK,aAAa;QACnD;QAEAT,GAAG,wCAAwC;YACzC,MAAMC,UAAUhB,kBAAkB;YAClCa,QAAQI,eAAe,CAACD;YACxBE,OAAOL,QAAQM,UAAU,CAAC,aAAaC,IAAI,CAACJ;QAC9C;IACF;IAEAJ,SAAS,YAAY;QACnBG,GAAG,kDAAkD;YACnD,MAAMU,WAAWzB,kBAAkB;YACnC,MAAM0B,QAAQ1B,kBAAkB;YAChCa,QAAQI,eAAe,CAACQ;YACxBZ,QAAQI,eAAe,CAACS;YAExB,MAAMb,QAAQc,QAAQ;YAEtBT,OAAOO,SAASvB,KAAK,EAAE0B,qBAAqB,CAAC;YAC7CV,OAAOQ,MAAMxB,KAAK,EAAE0B,qBAAqB,CAAC;QAC5C;QAEAb,GAAG,gCAAgC;YACjC,MAAMG,OAAOL,QAAQc,QAAQ,IAAIE,QAAQ,CAACL,aAAa;QACzD;IACF;IAEAZ,SAAS,WAAW;QAClBG,GAAG,iDAAiD;YAClD,MAAMU,WAAWzB,kBAAkB;YACnC,MAAM0B,QAAQ1B,kBAAkB;YAChCa,QAAQI,eAAe,CAACQ;YACxBZ,QAAQI,eAAe,CAACS;YAExB,MAAMb,QAAQiB,OAAO;YAErBZ,OAAOO,SAASlB,IAAI,EAAEqB,qBAAqB,CAAC;YAC5CV,OAAOQ,MAAMnB,IAAI,EAAEqB,qBAAqB,CAAC;QAC3C;QAEAb,GAAG,gCAAgC;YACjC,MAAMG,OAAOL,QAAQiB,OAAO,IAAID,QAAQ,CAACL,aAAa;QACxD;IACF;AACF"}
|