@urugus/slack-cli 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.claude/settings.local.json +13 -55
  2. package/.github/workflows/ci.yml +2 -2
  3. package/dist/commands/history-display.d.ts +1 -1
  4. package/dist/commands/history-display.d.ts.map +1 -1
  5. package/dist/commands/history-display.js +8 -28
  6. package/dist/commands/history-display.js.map +1 -1
  7. package/dist/commands/history.d.ts.map +1 -1
  8. package/dist/commands/history.js +9 -2
  9. package/dist/commands/history.js.map +1 -1
  10. package/dist/commands/unread.d.ts.map +1 -1
  11. package/dist/commands/unread.js +32 -16
  12. package/dist/commands/unread.js.map +1 -1
  13. package/dist/index.js +0 -0
  14. package/dist/types/commands.d.ts +1 -0
  15. package/dist/types/commands.d.ts.map +1 -1
  16. package/dist/utils/errors.d.ts.map +1 -1
  17. package/dist/utils/errors.js +1 -5
  18. package/dist/utils/errors.js.map +1 -1
  19. package/dist/utils/formatters/history-formatters.d.ts +8 -0
  20. package/dist/utils/formatters/history-formatters.d.ts.map +1 -0
  21. package/dist/utils/formatters/history-formatters.js +105 -0
  22. package/dist/utils/formatters/history-formatters.js.map +1 -0
  23. package/dist/utils/validators.d.ts +4 -0
  24. package/dist/utils/validators.d.ts.map +1 -1
  25. package/dist/utils/validators.js +12 -0
  26. package/dist/utils/validators.js.map +1 -1
  27. package/eslint.config.js +38 -0
  28. package/package.json +12 -14
  29. package/src/commands/history-display.ts +9 -28
  30. package/src/commands/history.ts +9 -2
  31. package/src/commands/unread.ts +52 -22
  32. package/src/types/commands.ts +1 -0
  33. package/src/utils/errors.ts +1 -5
  34. package/src/utils/formatters/history-formatters.ts +123 -0
  35. package/src/utils/validators.ts +13 -0
  36. package/tests/commands/history.test.ts +115 -0
  37. package/tests/index.test.ts +2 -2
  38. package/.eslintrc.json +0 -25
  39. package/dist/utils/config.d.ts +0 -10
  40. package/dist/utils/config.d.ts.map +0 -1
  41. package/dist/utils/config.js +0 -94
  42. package/dist/utils/config.js.map +0 -1
  43. package/dist/utils/formatters/output-formatter.d.ts +0 -7
  44. package/dist/utils/formatters/output-formatter.d.ts.map +0 -1
  45. package/dist/utils/formatters/output-formatter.js +0 -7
  46. package/dist/utils/formatters/output-formatter.js.map +0 -1
  47. package/dist/utils/profile-config-refactored.d.ts +0 -20
  48. package/dist/utils/profile-config-refactored.d.ts.map +0 -1
  49. package/dist/utils/profile-config-refactored.js +0 -174
  50. package/dist/utils/profile-config-refactored.js.map +0 -1
  51. package/src/utils/formatters/output-formatter.ts +0 -7
  52. package/tests/utils/slack-operations/channel-operations-refactored.test.ts +0 -179
@@ -1,94 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.ConfigManager = void 0;
37
- const fs = __importStar(require("fs/promises"));
38
- const path = __importStar(require("path"));
39
- const os = __importStar(require("os"));
40
- class ConfigManager {
41
- constructor(options = {}) {
42
- const configDir = options.configDir || path.join(os.homedir(), '.slack-cli');
43
- this.configPath = path.join(configDir, 'config.json');
44
- }
45
- async setToken(token) {
46
- const config = {
47
- token,
48
- updatedAt: new Date().toISOString(),
49
- };
50
- const configDir = path.dirname(this.configPath);
51
- await fs.mkdir(configDir, { recursive: true });
52
- await fs.writeFile(this.configPath, JSON.stringify(config, null, 2));
53
- await fs.chmod(this.configPath, 0o600);
54
- }
55
- async getConfig() {
56
- try {
57
- const data = await fs.readFile(this.configPath, 'utf-8');
58
- const config = JSON.parse(data);
59
- if (!config.token || !config.updatedAt) {
60
- throw new Error('Invalid config file format');
61
- }
62
- return config;
63
- }
64
- catch (error) {
65
- if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
66
- return null;
67
- }
68
- if (error instanceof SyntaxError) {
69
- throw new Error('Invalid config file format');
70
- }
71
- throw error;
72
- }
73
- }
74
- async clearConfig() {
75
- try {
76
- await fs.unlink(this.configPath);
77
- }
78
- catch (error) {
79
- if (error && typeof error === 'object' && 'code' in error && error.code !== 'ENOENT') {
80
- throw error;
81
- }
82
- }
83
- }
84
- maskToken(token) {
85
- if (token.length <= 9) {
86
- return '****';
87
- }
88
- const prefix = token.substring(0, 4);
89
- const suffix = token.substring(token.length - 4);
90
- return `${prefix}-****-****-${suffix}`;
91
- }
92
- }
93
- exports.ConfigManager = ConfigManager;
94
- //# sourceMappingURL=config.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,gDAAkC;AAClC,2CAA6B;AAC7B,uCAAyB;AAGzB,MAAa,aAAa;IAGxB,YAAY,UAAyB,EAAE;QACrC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,CAAC;QAC7E,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,KAAa;QAC1B,MAAM,MAAM,GAAW;YACrB,KAAK;YACL,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC;QAEF,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAChD,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAE/C,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACrE,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,SAAS;QACb,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YACzD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAEhC,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;gBACvC,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;YAChD,CAAC;YAED,OAAO,MAAgB,CAAC;QAC1B,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACrF,OAAO,IAAI,CAAC;YACd,CAAC;YACD,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;gBACjC,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;YAChD,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,WAAW;QACf,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACnC,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACrF,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAED,SAAS,CAAC,KAAa;QACrB,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YACtB,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACrC,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAEjD,OAAO,GAAG,MAAM,cAAc,MAAM,EAAE,CAAC;IACzC,CAAC;CACF;AA9DD,sCA8DC"}
@@ -1,7 +0,0 @@
1
- export interface OutputFormatter<T> {
2
- format(data: T[]): void;
3
- }
4
- export declare abstract class BaseFormatter<T> implements OutputFormatter<T> {
5
- abstract format(data: T[]): void;
6
- }
7
- //# sourceMappingURL=output-formatter.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"output-formatter.d.ts","sourceRoot":"","sources":["../../../src/utils/formatters/output-formatter.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC;CACzB;AAED,8BAAsB,aAAa,CAAC,CAAC,CAAE,YAAW,eAAe,CAAC,CAAC,CAAC;IAClE,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI;CACjC"}
@@ -1,7 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BaseFormatter = void 0;
4
- class BaseFormatter {
5
- }
6
- exports.BaseFormatter = BaseFormatter;
7
- //# sourceMappingURL=output-formatter.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"output-formatter.js","sourceRoot":"","sources":["../../../src/utils/formatters/output-formatter.ts"],"names":[],"mappings":";;;AAIA,MAAsB,aAAa;CAElC;AAFD,sCAEC"}
@@ -1,20 +0,0 @@
1
- import type { Config, ConfigOptions, Profile } from '../types/config';
2
- export declare class ProfileConfigManager {
3
- private fileManager;
4
- private cryptoService;
5
- private profileManager;
6
- constructor(_options?: ConfigOptions);
7
- setToken(token: string, profile?: string): Promise<void>;
8
- getConfig(profile?: string): Promise<Config | null>;
9
- listProfiles(): Promise<Profile[]>;
10
- useProfile(profile: string): Promise<void>;
11
- getCurrentProfile(): Promise<string>;
12
- clearConfig(profile?: string): Promise<void>;
13
- maskToken(token: string): string;
14
- migrateIfNeeded(): Promise<void>;
15
- }
16
- export declare const profileConfig: {
17
- getCurrentProfile: () => string;
18
- getToken: (_profile?: string) => string | undefined;
19
- };
20
- //# sourceMappingURL=profile-config-refactored.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"profile-config-refactored.d.ts","sourceRoot":"","sources":["../../src/utils/profile-config-refactored.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAOtE,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,cAAc,CAAiB;gBAE3B,QAAQ,GAAE,aAAkB;IAQlC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBxD,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAcnD,YAAY,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAiBlC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS1C,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;IAIpC,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgClD,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAY1B,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;CAwBvC;AAGD,eAAO,MAAM,aAAa;6BACD,MAAM;0BAGP,MAAM,KAAG,MAAM,GAAG,SAAS;CAKlD,CAAC"}
@@ -1,174 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.profileConfig = exports.ProfileConfigManager = void 0;
37
- const constants_1 = require("./constants");
38
- const config_file_manager_1 = require("./config/config-file-manager");
39
- const token_crypto_service_1 = require("./config/token-crypto-service");
40
- const profile_manager_1 = require("./config/profile-manager");
41
- const fs = __importStar(require("fs/promises"));
42
- class ProfileConfigManager {
43
- constructor(_options = {}) {
44
- // Note: ConfigFileManager currently doesn't support custom configDir
45
- // This would need to be added if required
46
- this.fileManager = new config_file_manager_1.ConfigFileManager();
47
- this.cryptoService = new token_crypto_service_1.TokenCryptoService();
48
- this.profileManager = new profile_manager_1.ProfileManager(this.fileManager, this.cryptoService);
49
- }
50
- async setToken(token, profile) {
51
- const profileName = profile || (await this.profileManager.getCurrentProfile());
52
- const config = {
53
- token,
54
- updatedAt: new Date().toISOString(),
55
- };
56
- await this.profileManager.setProfile(profileName, config);
57
- // Set as default profile if it's the first one or explicitly setting default
58
- const profiles = await this.profileManager.listProfiles();
59
- if (profiles.length === 1 || profileName === constants_1.DEFAULT_PROFILE_NAME) {
60
- await this.profileManager.setCurrentProfile(profileName);
61
- }
62
- }
63
- async getConfig(profile) {
64
- const profileName = profile || (await this.profileManager.getCurrentProfile());
65
- try {
66
- return await this.profileManager.getProfile(profileName);
67
- }
68
- catch (error) {
69
- // Return null if profile not found
70
- if (error instanceof Error && error.message.includes('not found')) {
71
- return null;
72
- }
73
- throw error;
74
- }
75
- }
76
- async listProfiles() {
77
- const profileNames = await this.profileManager.listProfiles();
78
- const currentProfile = await this.profileManager.getCurrentProfile();
79
- const profiles = [];
80
- for (const name of profileNames) {
81
- const config = await this.profileManager.getProfile(name);
82
- profiles.push({
83
- name,
84
- config,
85
- isDefault: name === currentProfile,
86
- });
87
- }
88
- return profiles;
89
- }
90
- async useProfile(profile) {
91
- const exists = await this.profileManager.profileExists(profile);
92
- if (!exists) {
93
- throw new Error(`Profile "${profile}" does not exist`);
94
- }
95
- await this.profileManager.setCurrentProfile(profile);
96
- }
97
- async getCurrentProfile() {
98
- return await this.profileManager.getCurrentProfile();
99
- }
100
- async clearConfig(profile) {
101
- const profileName = profile || (await this.profileManager.getCurrentProfile());
102
- try {
103
- await this.profileManager.deleteProfile(profileName);
104
- }
105
- catch (error) {
106
- // If profile doesn't exist, do nothing
107
- if (error instanceof Error && error.message.includes('not found')) {
108
- return;
109
- }
110
- throw error;
111
- }
112
- // If we deleted the current profile, set a new default
113
- const currentProfile = await this.profileManager.getCurrentProfile();
114
- if (currentProfile === profileName) {
115
- const remainingProfiles = await this.profileManager.listProfiles();
116
- if (remainingProfiles.length > 0) {
117
- await this.profileManager.setCurrentProfile(remainingProfiles[0]);
118
- }
119
- else {
120
- // No profiles left, delete the config file
121
- try {
122
- await fs.unlink(this.fileManager.getConfigPath());
123
- }
124
- catch (error) {
125
- if (error && typeof error === 'object' && 'code' in error && error.code !== 'ENOENT') {
126
- throw error;
127
- }
128
- }
129
- }
130
- }
131
- }
132
- maskToken(token) {
133
- if (token.length <= constants_1.TOKEN_MIN_LENGTH) {
134
- return '****';
135
- }
136
- const prefix = token.substring(0, constants_1.TOKEN_MASK_LENGTH);
137
- const suffix = token.substring(token.length - constants_1.TOKEN_MASK_LENGTH);
138
- return `${prefix}-****-****-${suffix}`;
139
- }
140
- // Migration support - to be called separately if needed
141
- async migrateIfNeeded() {
142
- const data = await this.fileManager.read();
143
- // Check if migration is needed (old format detection)
144
- const anyData = data;
145
- if (anyData.token && !anyData.profiles) {
146
- // Old format detected, migrate
147
- const oldConfig = {
148
- token: anyData.token,
149
- updatedAt: anyData.updatedAt || new Date().toISOString(),
150
- };
151
- // Create new format
152
- const newData = {
153
- profiles: { [constants_1.DEFAULT_PROFILE_NAME]: oldConfig },
154
- currentProfile: constants_1.DEFAULT_PROFILE_NAME,
155
- };
156
- await this.fileManager.write(newData);
157
- // Re-encrypt token using new service
158
- await this.setToken(oldConfig.token, constants_1.DEFAULT_PROFILE_NAME);
159
- }
160
- }
161
- }
162
- exports.ProfileConfigManager = ProfileConfigManager;
163
- // Export a simplified version for backward compatibility
164
- exports.profileConfig = {
165
- getCurrentProfile: () => {
166
- return constants_1.DEFAULT_PROFILE_NAME;
167
- },
168
- getToken: (_profile) => {
169
- // This is a simplified version for testing
170
- // In real usage, it would need to be async
171
- return undefined;
172
- },
173
- };
174
- //# sourceMappingURL=profile-config-refactored.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"profile-config-refactored.js","sourceRoot":"","sources":["../../src/utils/profile-config-refactored.ts"],"names":[],"mappings":"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"}
@@ -1,7 +0,0 @@
1
- export interface OutputFormatter<T> {
2
- format(data: T[]): void;
3
- }
4
-
5
- export abstract class BaseFormatter<T> implements OutputFormatter<T> {
6
- abstract format(data: T[]): void;
7
- }
@@ -1,179 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import { ChannelOperations } from '../../../src/utils/slack-operations/channel-operations';
3
- import { WebClient } from '@slack/web-api';
4
-
5
- vi.mock('@slack/web-api');
6
-
7
- describe('ChannelOperations - refactored listUnreadChannels', () => {
8
- let channelOps: ChannelOperations;
9
- let mockClient: any;
10
-
11
- beforeEach(() => {
12
- vi.resetAllMocks();
13
- mockClient = {
14
- conversations: {
15
- list: vi.fn(),
16
- info: vi.fn(),
17
- history: vi.fn(),
18
- },
19
- };
20
- channelOps = new ChannelOperations(mockClient as WebClient);
21
- });
22
-
23
- describe('listUnreadChannels', () => {
24
- it('should fetch unread channels with proper separation of concerns', async () => {
25
- const mockChannels = [
26
- { id: 'C1', name: 'general' },
27
- { id: 'C2', name: 'random' },
28
- ];
29
-
30
- mockClient.conversations.list.mockResolvedValueOnce({
31
- channels: mockChannels,
32
- });
33
-
34
- // Channel 1 - has unread messages
35
- mockClient.conversations.info.mockResolvedValueOnce({
36
- channel: { id: 'C1', last_read: '1234567890.000000' },
37
- });
38
- mockClient.conversations.history
39
- .mockResolvedValueOnce({ messages: [{ ts: '1234567900.000000' }] }) // latest message
40
- .mockResolvedValueOnce({ messages: [{ ts: '1234567900.000000' }, { ts: '1234567895.000000' }] }); // messages after last_read
41
-
42
- // Channel 2 - no unread messages
43
- mockClient.conversations.info.mockResolvedValueOnce({
44
- channel: { id: 'C2', last_read: '1234567900.000000' },
45
- });
46
- mockClient.conversations.history
47
- .mockResolvedValueOnce({ messages: [{ ts: '1234567890.000000' }] }) // latest message
48
- .mockResolvedValueOnce({ messages: [] }); // no messages after last_read
49
-
50
- const result = await channelOps.listUnreadChannels();
51
-
52
- expect(result).toHaveLength(1);
53
- expect(result[0]).toMatchObject({
54
- id: 'C1',
55
- name: 'general',
56
- unread_count: 2,
57
- unread_count_display: 2,
58
- });
59
-
60
- // Verify the separation of concerns - each method is called appropriately
61
- expect(mockClient.conversations.list).toHaveBeenCalledOnce();
62
- expect(mockClient.conversations.info).toHaveBeenCalledTimes(2);
63
- expect(mockClient.conversations.history).toHaveBeenCalledTimes(4);
64
- });
65
-
66
- it('should handle channels with no last_read timestamp', async () => {
67
- const mockChannels = [
68
- { id: 'C1', name: 'general' },
69
- ];
70
-
71
- mockClient.conversations.list.mockResolvedValueOnce({
72
- channels: mockChannels,
73
- });
74
-
75
- mockClient.conversations.info.mockResolvedValueOnce({
76
- channel: { id: 'C1' }, // no last_read
77
- });
78
-
79
- mockClient.conversations.history
80
- .mockResolvedValueOnce({ messages: [{ ts: '1234567900.000000' }] }) // check if has messages
81
- .mockResolvedValueOnce({
82
- messages: [
83
- { ts: '1234567900.000000' },
84
- { ts: '1234567895.000000' },
85
- { ts: '1234567890.000000' },
86
- ]
87
- }); // all messages are unread
88
-
89
- const result = await channelOps.listUnreadChannels();
90
-
91
- expect(result).toHaveLength(1);
92
- expect(result[0]).toMatchObject({
93
- id: 'C1',
94
- name: 'general',
95
- unread_count: 3,
96
- unread_count_display: 3,
97
- });
98
- });
99
-
100
- it('should skip channels with no messages', async () => {
101
- const mockChannels = [
102
- { id: 'C1', name: 'general' },
103
- ];
104
-
105
- mockClient.conversations.list.mockResolvedValueOnce({
106
- channels: mockChannels,
107
- });
108
-
109
- mockClient.conversations.info.mockResolvedValueOnce({
110
- channel: { id: 'C1', last_read: '1234567890.000000' },
111
- });
112
-
113
- mockClient.conversations.history
114
- .mockResolvedValueOnce({ messages: [] }); // no messages at all
115
-
116
- const result = await channelOps.listUnreadChannels();
117
-
118
- expect(result).toHaveLength(0);
119
- });
120
-
121
- it('should handle rate limit errors gracefully', async () => {
122
- const mockChannels = [
123
- { id: 'C1', name: 'general' },
124
- { id: 'C2', name: 'random' },
125
- ];
126
-
127
- mockClient.conversations.list.mockResolvedValueOnce({
128
- channels: mockChannels,
129
- });
130
-
131
- // Channel 1 - rate limited
132
- mockClient.conversations.info.mockRejectedValueOnce(new Error('rate_limited'));
133
-
134
- // Channel 2 - successful
135
- mockClient.conversations.info.mockResolvedValueOnce({
136
- channel: { id: 'C2', last_read: '1234567890.000000' },
137
- });
138
- mockClient.conversations.history
139
- .mockResolvedValueOnce({ messages: [{ ts: '1234567900.000000' }] })
140
- .mockResolvedValueOnce({ messages: [{ ts: '1234567900.000000' }] });
141
-
142
- const result = await channelOps.listUnreadChannels();
143
-
144
- expect(result).toHaveLength(1);
145
- expect(result[0].id).toBe('C2');
146
- });
147
- });
148
-
149
- describe('private methods (indirectly tested)', () => {
150
- it('fetchAllChannels should handle large channel lists', async () => {
151
- const mockChannels = [
152
- { id: 'C1', name: 'channel-1' },
153
- { id: 'C2', name: 'channel-2' },
154
- { id: 'C3', name: 'channel-3' },
155
- ];
156
-
157
- mockClient.conversations.list.mockResolvedValueOnce({
158
- channels: mockChannels,
159
- });
160
-
161
- // Mock all channels having no unread messages for simplicity
162
- mockChannels.forEach(() => {
163
- mockClient.conversations.info.mockResolvedValueOnce({
164
- channel: { last_read: '9999999999.000000' },
165
- });
166
- mockClient.conversations.history.mockResolvedValueOnce({ messages: [] });
167
- });
168
-
169
- const result = await channelOps.listUnreadChannels();
170
-
171
- expect(result).toHaveLength(0);
172
- expect(mockClient.conversations.list).toHaveBeenCalledWith({
173
- types: 'public_channel,private_channel,im,mpim',
174
- exclude_archived: true,
175
- limit: 1000,
176
- });
177
- });
178
- });
179
- });