@cliangdev/flux-plugin 0.0.0-dev.cbdf207 → 0.0.0-dev.df3e9bb
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 +8 -4
- package/bin/install.cjs +150 -16
- package/package.json +7 -11
- package/src/__tests__/version.test.ts +37 -0
- package/src/adapters/local/.gitkeep +0 -0
- package/src/server/__tests__/config.test.ts +163 -0
- package/src/server/adapters/__tests__/a-client-linear.test.ts +197 -0
- package/src/server/adapters/__tests__/adapter-factory.test.ts +230 -0
- package/src/server/adapters/__tests__/dependency-ops.test.ts +395 -0
- package/src/server/adapters/__tests__/document-ops.test.ts +306 -0
- package/src/server/adapters/__tests__/linear-adapter.test.ts +91 -0
- package/src/server/adapters/__tests__/linear-config.test.ts +425 -0
- package/src/server/adapters/__tests__/linear-criteria-parser.test.ts +287 -0
- package/src/server/adapters/__tests__/linear-description-test.ts +238 -0
- package/src/server/adapters/__tests__/linear-epic-crud.test.ts +496 -0
- package/src/server/adapters/__tests__/linear-mappers-description.test.ts +276 -0
- package/src/server/adapters/__tests__/linear-mappers-epic.test.ts +294 -0
- package/src/server/adapters/__tests__/linear-mappers-prd.test.ts +300 -0
- package/src/server/adapters/__tests__/linear-mappers-task.test.ts +197 -0
- package/src/server/adapters/__tests__/linear-prd-crud.test.ts +620 -0
- package/src/server/adapters/__tests__/linear-stats.test.ts +450 -0
- package/src/server/adapters/__tests__/linear-task-crud.test.ts +534 -0
- package/src/server/adapters/__tests__/linear-types.test.ts +243 -0
- package/src/server/adapters/__tests__/status-ops.test.ts +441 -0
- package/src/server/adapters/factory.ts +90 -0
- package/src/server/adapters/index.ts +9 -0
- package/src/server/adapters/linear/adapter.ts +1136 -0
- package/src/server/adapters/linear/client.ts +169 -0
- package/src/server/adapters/linear/config.ts +152 -0
- package/src/server/adapters/linear/helpers/criteria-parser.ts +197 -0
- package/src/server/adapters/linear/helpers/index.ts +7 -0
- package/src/server/adapters/linear/index.ts +16 -0
- package/src/server/adapters/linear/mappers/description.ts +136 -0
- package/src/server/adapters/linear/mappers/epic.ts +81 -0
- package/src/server/adapters/linear/mappers/index.ts +27 -0
- package/src/server/adapters/linear/mappers/prd.ts +178 -0
- package/src/server/adapters/linear/mappers/task.ts +82 -0
- package/src/server/adapters/linear/types.ts +264 -0
- package/src/server/adapters/local-adapter.ts +968 -0
- package/src/server/adapters/types.ts +293 -0
- package/src/server/config.ts +73 -0
- package/src/server/db/__tests__/queries.test.ts +472 -0
- package/src/server/db/ids.ts +17 -0
- package/src/server/db/index.ts +69 -0
- package/src/server/db/queries.ts +142 -0
- package/src/server/db/refs.ts +60 -0
- package/src/server/db/schema.ts +88 -0
- package/src/server/db/sqlite.ts +10 -0
- package/src/server/index.ts +83 -0
- package/src/server/tools/__tests__/crud.test.ts +301 -0
- package/src/server/tools/__tests__/get-version.test.ts +27 -0
- package/src/server/tools/__tests__/mcp-interface.test.ts +388 -0
- package/src/server/tools/__tests__/query.test.ts +353 -0
- package/src/server/tools/__tests__/z-configure-linear.test.ts +511 -0
- package/src/server/tools/__tests__/z-get-linear-url.test.ts +108 -0
- package/src/server/tools/configure-linear.ts +373 -0
- package/src/server/tools/create-epic.ts +35 -0
- package/src/server/tools/create-prd.ts +31 -0
- package/src/server/tools/create-task.ts +38 -0
- package/src/server/tools/criteria.ts +50 -0
- package/src/server/tools/delete-entity.ts +76 -0
- package/src/server/tools/dependencies.ts +55 -0
- package/src/server/tools/get-entity.ts +238 -0
- package/src/server/tools/get-linear-url.ts +28 -0
- package/src/server/tools/get-project-context.ts +33 -0
- package/src/server/tools/get-stats.ts +52 -0
- package/src/server/tools/get-version.ts +20 -0
- package/src/server/tools/index.ts +114 -0
- package/src/server/tools/init-project.ts +108 -0
- package/src/server/tools/query-entities.ts +167 -0
- package/src/server/tools/render-status.ts +201 -0
- package/src/server/tools/update-entity.ts +140 -0
- package/src/server/tools/update-status.ts +166 -0
- package/src/server/utils/__tests__/mcp-response.test.ts +331 -0
- package/src/server/utils/logger.ts +9 -0
- package/src/server/utils/mcp-response.ts +254 -0
- package/src/server/utils/status-transitions.ts +160 -0
- package/src/status-line/__tests__/status-line.test.ts +215 -0
- package/src/status-line/index.ts +147 -0
- package/src/utils/__tests__/chalk-import.test.ts +32 -0
- package/src/utils/__tests__/display.test.ts +97 -0
- package/src/utils/__tests__/status-renderer.test.ts +310 -0
- package/src/utils/display.ts +62 -0
- package/src/utils/status-renderer.ts +188 -0
- package/src/version.ts +5 -0
- package/dist/server/index.js +0 -87063
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
2
|
+
import type { HydratedIssue } from "../linear/adapter.js";
|
|
3
|
+
import type { LinearConfig } from "../linear/index.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Helper to create a mock HydratedIssue
|
|
7
|
+
*/
|
|
8
|
+
function createMockIssue(
|
|
9
|
+
overrides: Partial<HydratedIssue> = {},
|
|
10
|
+
): HydratedIssue {
|
|
11
|
+
return {
|
|
12
|
+
id: "issue_abc123",
|
|
13
|
+
identifier: "ENG-42",
|
|
14
|
+
title: "Test Issue",
|
|
15
|
+
description: "Test description",
|
|
16
|
+
stateName: "Backlog",
|
|
17
|
+
stateType: "backlog",
|
|
18
|
+
labels: ["epic"],
|
|
19
|
+
parentIdentifier: "ENG-1",
|
|
20
|
+
priority: 3,
|
|
21
|
+
createdAt: new Date("2024-01-01T00:00:00Z"),
|
|
22
|
+
updatedAt: new Date("2024-01-01T00:00:00Z"),
|
|
23
|
+
_raw: {},
|
|
24
|
+
...overrides,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe("LinearAdapter", () => {
|
|
29
|
+
const mockConfig: LinearConfig = {
|
|
30
|
+
apiKey: "lin_api_test123",
|
|
31
|
+
teamId: "TEAM-123",
|
|
32
|
+
projectId: "proj_container",
|
|
33
|
+
defaultLabels: {
|
|
34
|
+
prd: "prd",
|
|
35
|
+
epic: "epic",
|
|
36
|
+
task: "task",
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
describe("Instantiation", () => {
|
|
41
|
+
test("can be instantiated with valid config", async () => {
|
|
42
|
+
const { LinearAdapter: LA } = await import("../linear/adapter.js");
|
|
43
|
+
const adapter = new LA(mockConfig);
|
|
44
|
+
expect(adapter).toBeInstanceOf(LA);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("addCriterion", () => {
|
|
49
|
+
test("adds criterion to epic description", async () => {
|
|
50
|
+
const { LinearAdapter: LA } = await import("../linear/adapter.js");
|
|
51
|
+
const adapter = new LA(mockConfig);
|
|
52
|
+
|
|
53
|
+
const mockEpic = createMockIssue({
|
|
54
|
+
id: "issue_epic_123",
|
|
55
|
+
identifier: "ENG-42",
|
|
56
|
+
title: "Test Epic",
|
|
57
|
+
description: "Epic description",
|
|
58
|
+
labels: ["epic"],
|
|
59
|
+
parentIdentifier: "ENG-1",
|
|
60
|
+
_raw: {
|
|
61
|
+
update: mock(async () => ({})),
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
(adapter as any).fetchIssue = mock(async () => mockEpic);
|
|
66
|
+
(adapter as any).client = {
|
|
67
|
+
execute: mock(async (fn: () => Promise<any>) => fn()),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const criterion = await adapter.addCriterion({
|
|
71
|
+
parentRef: "ENG-42",
|
|
72
|
+
criteria: "User can login",
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(criterion.parentId).toBe("ENG-42");
|
|
76
|
+
expect(criterion.criteria).toBe("User can login");
|
|
77
|
+
expect(criterion.isMet).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("throws error if parent not found", async () => {
|
|
81
|
+
const { LinearAdapter: LA } = await import("../linear/adapter.js");
|
|
82
|
+
const adapter = new LA(mockConfig);
|
|
83
|
+
|
|
84
|
+
(adapter as any).fetchIssue = mock(async () => null);
|
|
85
|
+
|
|
86
|
+
await expect(
|
|
87
|
+
adapter.addCriterion({ parentRef: "ENG-999", criteria: "test" }),
|
|
88
|
+
).rejects.toThrow("Entity not found: ENG-999");
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
realpathSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
|
|
12
|
+
describe("Linear Config", () => {
|
|
13
|
+
const originalEnv = process.env.FLUX_PROJECT_ROOT;
|
|
14
|
+
const TEST_DIR = `${realpathSync(tmpdir())}/flux-linear-config-test-${Date.now()}`;
|
|
15
|
+
const FLUX_DIR = `${TEST_DIR}/.flux`;
|
|
16
|
+
const CONFIG_PATH = `${FLUX_DIR}/linear-config.json`;
|
|
17
|
+
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
// Clean up any previous test directory
|
|
20
|
+
if (existsSync(TEST_DIR)) {
|
|
21
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Create test directory structure
|
|
25
|
+
mkdirSync(FLUX_DIR, { recursive: true });
|
|
26
|
+
|
|
27
|
+
// Set project root to test directory
|
|
28
|
+
process.env.FLUX_PROJECT_ROOT = TEST_DIR;
|
|
29
|
+
|
|
30
|
+
// Clear the config cache
|
|
31
|
+
const { config } = await import("../../config.js");
|
|
32
|
+
config.clearCache();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(async () => {
|
|
36
|
+
// Restore original env
|
|
37
|
+
if (originalEnv !== undefined) {
|
|
38
|
+
process.env.FLUX_PROJECT_ROOT = originalEnv;
|
|
39
|
+
} else {
|
|
40
|
+
delete process.env.FLUX_PROJECT_ROOT;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Clear the config cache
|
|
44
|
+
const { config } = await import("../../config.js");
|
|
45
|
+
config.clearCache();
|
|
46
|
+
|
|
47
|
+
// Clean up test directory
|
|
48
|
+
if (existsSync(TEST_DIR)) {
|
|
49
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("linearConfigExists", () => {
|
|
54
|
+
test("returns true when config file exists", async () => {
|
|
55
|
+
writeFileSync(
|
|
56
|
+
CONFIG_PATH,
|
|
57
|
+
JSON.stringify({
|
|
58
|
+
apiKey: "lin_api_test",
|
|
59
|
+
teamId: "TEAM-123",
|
|
60
|
+
projectId: "proj_123",
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const { linearConfigExists } = await import("../linear/config.js");
|
|
65
|
+
expect(linearConfigExists()).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("returns false when config file does not exist", async () => {
|
|
69
|
+
const { linearConfigExists } = await import("../linear/config.js");
|
|
70
|
+
expect(linearConfigExists()).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("validateLinearConfig", () => {
|
|
75
|
+
test("validates valid config with all fields", async () => {
|
|
76
|
+
const validConfig = {
|
|
77
|
+
apiKey: "lin_api_test123",
|
|
78
|
+
teamId: "TEAM-123",
|
|
79
|
+
projectId: "proj_abc123",
|
|
80
|
+
defaultLabels: {
|
|
81
|
+
prd: "prd",
|
|
82
|
+
epic: "epic",
|
|
83
|
+
task: "task",
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const { validateLinearConfig } = await import("../linear/config.js");
|
|
88
|
+
const result = validateLinearConfig(validConfig);
|
|
89
|
+
|
|
90
|
+
expect(result.apiKey).toBe("lin_api_test123");
|
|
91
|
+
expect(result.teamId).toBe("TEAM-123");
|
|
92
|
+
expect(result.projectId).toBe("proj_abc123");
|
|
93
|
+
expect(result.defaultLabels.prd).toBe("prd");
|
|
94
|
+
expect(result.defaultLabels.epic).toBe("epic");
|
|
95
|
+
expect(result.defaultLabels.task).toBe("task");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("validates valid config with optional defaultLabels omitted", async () => {
|
|
99
|
+
const validConfig = {
|
|
100
|
+
apiKey: "lin_api_test123",
|
|
101
|
+
teamId: "TEAM-123",
|
|
102
|
+
projectId: "proj_abc123",
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const { validateLinearConfig } = await import("../linear/config.js");
|
|
106
|
+
const result = validateLinearConfig(validConfig);
|
|
107
|
+
|
|
108
|
+
expect(result.apiKey).toBe("lin_api_test123");
|
|
109
|
+
expect(result.teamId).toBe("TEAM-123");
|
|
110
|
+
expect(result.projectId).toBe("proj_abc123");
|
|
111
|
+
expect(result.defaultLabels.prd).toBe("prd");
|
|
112
|
+
expect(result.defaultLabels.epic).toBe("epic");
|
|
113
|
+
expect(result.defaultLabels.task).toBe("task");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("throws error when apiKey is missing", async () => {
|
|
117
|
+
const invalidConfig = {
|
|
118
|
+
teamId: "TEAM-123",
|
|
119
|
+
projectId: "proj_abc123",
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const { validateLinearConfig } = await import("../linear/config.js");
|
|
123
|
+
|
|
124
|
+
expect(() => validateLinearConfig(invalidConfig)).toThrow(
|
|
125
|
+
"Invalid Linear config: apiKey is required",
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("throws error when teamId is missing", async () => {
|
|
130
|
+
const invalidConfig = {
|
|
131
|
+
apiKey: "lin_api_test123",
|
|
132
|
+
projectId: "proj_abc123",
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const { validateLinearConfig } = await import("../linear/config.js");
|
|
136
|
+
|
|
137
|
+
expect(() => validateLinearConfig(invalidConfig)).toThrow(
|
|
138
|
+
"Invalid Linear config: teamId is required",
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("throws error when projectId is missing", async () => {
|
|
143
|
+
const invalidConfig = {
|
|
144
|
+
apiKey: "lin_api_test123",
|
|
145
|
+
teamId: "TEAM-123",
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const { validateLinearConfig } = await import("../linear/config.js");
|
|
149
|
+
|
|
150
|
+
expect(() => validateLinearConfig(invalidConfig)).toThrow(
|
|
151
|
+
"Invalid Linear config: projectId is required",
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("throws error when config is not an object", async () => {
|
|
156
|
+
const { validateLinearConfig } = await import("../linear/config.js");
|
|
157
|
+
|
|
158
|
+
expect(() => validateLinearConfig(null)).toThrow(
|
|
159
|
+
"Invalid Linear config: must be an object",
|
|
160
|
+
);
|
|
161
|
+
expect(() => validateLinearConfig("string")).toThrow(
|
|
162
|
+
"Invalid Linear config: must be an object",
|
|
163
|
+
);
|
|
164
|
+
expect(() => validateLinearConfig(123)).toThrow(
|
|
165
|
+
"Invalid Linear config: must be an object",
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("throws error when apiKey is not a string", async () => {
|
|
170
|
+
const invalidConfig = {
|
|
171
|
+
apiKey: 123,
|
|
172
|
+
teamId: "TEAM-123",
|
|
173
|
+
projectId: "proj_abc123",
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const { validateLinearConfig } = await import("../linear/config.js");
|
|
177
|
+
|
|
178
|
+
expect(() => validateLinearConfig(invalidConfig)).toThrow(
|
|
179
|
+
"Invalid Linear config: apiKey must be a string",
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("throws error when teamId is not a string", async () => {
|
|
184
|
+
const invalidConfig = {
|
|
185
|
+
apiKey: "lin_api_test",
|
|
186
|
+
teamId: 123,
|
|
187
|
+
projectId: "proj_abc123",
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const { validateLinearConfig } = await import("../linear/config.js");
|
|
191
|
+
|
|
192
|
+
expect(() => validateLinearConfig(invalidConfig)).toThrow(
|
|
193
|
+
"Invalid Linear config: teamId must be a string",
|
|
194
|
+
);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("throws error when projectId is not a string", async () => {
|
|
198
|
+
const invalidConfig = {
|
|
199
|
+
apiKey: "lin_api_test",
|
|
200
|
+
teamId: "TEAM-123",
|
|
201
|
+
projectId: 123,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const { validateLinearConfig } = await import("../linear/config.js");
|
|
205
|
+
|
|
206
|
+
expect(() => validateLinearConfig(invalidConfig)).toThrow(
|
|
207
|
+
"Invalid Linear config: projectId must be a string",
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("uses default labels when defaultLabels is partially provided", async () => {
|
|
212
|
+
const configWithPartialLabels = {
|
|
213
|
+
apiKey: "lin_api_test",
|
|
214
|
+
teamId: "TEAM-123",
|
|
215
|
+
projectId: "proj_abc123",
|
|
216
|
+
defaultLabels: {
|
|
217
|
+
epic: "custom-epic",
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const { validateLinearConfig } = await import("../linear/config.js");
|
|
222
|
+
const result = validateLinearConfig(configWithPartialLabels);
|
|
223
|
+
|
|
224
|
+
expect(result.defaultLabels.prd).toBe("prd");
|
|
225
|
+
expect(result.defaultLabels.epic).toBe("custom-epic");
|
|
226
|
+
expect(result.defaultLabels.task).toBe("task");
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("allows custom prd label", async () => {
|
|
230
|
+
const configWithCustomPrdLabel = {
|
|
231
|
+
apiKey: "lin_api_test",
|
|
232
|
+
teamId: "TEAM-123",
|
|
233
|
+
projectId: "proj_abc123",
|
|
234
|
+
defaultLabels: {
|
|
235
|
+
prd: "custom-prd",
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const { validateLinearConfig } = await import("../linear/config.js");
|
|
240
|
+
const result = validateLinearConfig(configWithCustomPrdLabel);
|
|
241
|
+
|
|
242
|
+
expect(result.defaultLabels.prd).toBe("custom-prd");
|
|
243
|
+
expect(result.defaultLabels.epic).toBe("epic");
|
|
244
|
+
expect(result.defaultLabels.task).toBe("task");
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe("loadLinearConfig", () => {
|
|
249
|
+
test("loads valid config from file", async () => {
|
|
250
|
+
writeFileSync(
|
|
251
|
+
CONFIG_PATH,
|
|
252
|
+
JSON.stringify({
|
|
253
|
+
apiKey: "lin_api_test123",
|
|
254
|
+
teamId: "TEAM-123",
|
|
255
|
+
projectId: "proj_abc123",
|
|
256
|
+
defaultLabels: {
|
|
257
|
+
prd: "prd",
|
|
258
|
+
epic: "epic",
|
|
259
|
+
task: "task",
|
|
260
|
+
},
|
|
261
|
+
}),
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
const { loadLinearConfig } = await import("../linear/config.js");
|
|
265
|
+
const config = loadLinearConfig();
|
|
266
|
+
|
|
267
|
+
expect(config.apiKey).toBe("lin_api_test123");
|
|
268
|
+
expect(config.teamId).toBe("TEAM-123");
|
|
269
|
+
expect(config.projectId).toBe("proj_abc123");
|
|
270
|
+
expect(config.defaultLabels.prd).toBe("prd");
|
|
271
|
+
expect(config.defaultLabels.epic).toBe("epic");
|
|
272
|
+
expect(config.defaultLabels.task).toBe("task");
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("loads config with default labels when not provided", async () => {
|
|
276
|
+
writeFileSync(
|
|
277
|
+
CONFIG_PATH,
|
|
278
|
+
JSON.stringify({
|
|
279
|
+
apiKey: "lin_api_test123",
|
|
280
|
+
teamId: "TEAM-123",
|
|
281
|
+
projectId: "proj_abc123",
|
|
282
|
+
}),
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const { loadLinearConfig } = await import("../linear/config.js");
|
|
286
|
+
const config = loadLinearConfig();
|
|
287
|
+
|
|
288
|
+
expect(config.apiKey).toBe("lin_api_test123");
|
|
289
|
+
expect(config.teamId).toBe("TEAM-123");
|
|
290
|
+
expect(config.projectId).toBe("proj_abc123");
|
|
291
|
+
expect(config.defaultLabels.prd).toBe("prd");
|
|
292
|
+
expect(config.defaultLabels.epic).toBe("epic");
|
|
293
|
+
expect(config.defaultLabels.task).toBe("task");
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("throws error when config file does not exist", async () => {
|
|
297
|
+
const { loadLinearConfig } = await import("../linear/config.js");
|
|
298
|
+
|
|
299
|
+
expect(() => loadLinearConfig()).toThrow(
|
|
300
|
+
"Linear config not found. Run configure_linear first.",
|
|
301
|
+
);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("throws error when config file contains invalid JSON", async () => {
|
|
305
|
+
writeFileSync(CONFIG_PATH, "invalid json {");
|
|
306
|
+
|
|
307
|
+
const { loadLinearConfig } = await import("../linear/config.js");
|
|
308
|
+
|
|
309
|
+
expect(() => loadLinearConfig()).toThrow();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test("throws error when config is missing required fields", async () => {
|
|
313
|
+
writeFileSync(
|
|
314
|
+
CONFIG_PATH,
|
|
315
|
+
JSON.stringify({
|
|
316
|
+
apiKey: "lin_api_test123",
|
|
317
|
+
// missing teamId and projectId
|
|
318
|
+
}),
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
const { loadLinearConfig } = await import("../linear/config.js");
|
|
322
|
+
|
|
323
|
+
expect(() => loadLinearConfig()).toThrow(
|
|
324
|
+
"Invalid Linear config: teamId is required",
|
|
325
|
+
);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
describe("saveLinearConfig", () => {
|
|
330
|
+
test("saves valid config to file", async () => {
|
|
331
|
+
const config = {
|
|
332
|
+
apiKey: "lin_api_test123",
|
|
333
|
+
teamId: "TEAM-123",
|
|
334
|
+
projectId: "proj_abc123",
|
|
335
|
+
defaultLabels: {
|
|
336
|
+
prd: "prd",
|
|
337
|
+
epic: "epic",
|
|
338
|
+
task: "task",
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const { saveLinearConfig } = await import("../linear/config.js");
|
|
343
|
+
saveLinearConfig(config);
|
|
344
|
+
|
|
345
|
+
expect(existsSync(CONFIG_PATH)).toBe(true);
|
|
346
|
+
|
|
347
|
+
const saved = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
348
|
+
expect(saved.apiKey).toBe("lin_api_test123");
|
|
349
|
+
expect(saved.teamId).toBe("TEAM-123");
|
|
350
|
+
expect(saved.projectId).toBe("proj_abc123");
|
|
351
|
+
expect(saved.defaultLabels.prd).toBe("prd");
|
|
352
|
+
expect(saved.defaultLabels.epic).toBe("epic");
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test("creates .flux directory if it does not exist", async () => {
|
|
356
|
+
// Remove .flux directory
|
|
357
|
+
rmSync(FLUX_DIR, { recursive: true });
|
|
358
|
+
|
|
359
|
+
const config = {
|
|
360
|
+
apiKey: "lin_api_test123",
|
|
361
|
+
teamId: "TEAM-123",
|
|
362
|
+
projectId: "proj_abc123",
|
|
363
|
+
defaultLabels: {
|
|
364
|
+
prd: "prd",
|
|
365
|
+
epic: "epic",
|
|
366
|
+
task: "task",
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const { saveLinearConfig } = await import("../linear/config.js");
|
|
371
|
+
saveLinearConfig(config);
|
|
372
|
+
|
|
373
|
+
expect(existsSync(FLUX_DIR)).toBe(true);
|
|
374
|
+
expect(existsSync(CONFIG_PATH)).toBe(true);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test("overwrites existing config file", async () => {
|
|
378
|
+
writeFileSync(
|
|
379
|
+
CONFIG_PATH,
|
|
380
|
+
JSON.stringify({
|
|
381
|
+
apiKey: "old_api_key",
|
|
382
|
+
teamId: "OLD-TEAM",
|
|
383
|
+
projectId: "old_proj",
|
|
384
|
+
}),
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
const newConfig = {
|
|
388
|
+
apiKey: "new_api_key",
|
|
389
|
+
teamId: "NEW-TEAM",
|
|
390
|
+
projectId: "new_proj",
|
|
391
|
+
defaultLabels: {
|
|
392
|
+
prd: "custom-prd",
|
|
393
|
+
epic: "custom-epic",
|
|
394
|
+
task: "custom-task",
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const { saveLinearConfig } = await import("../linear/config.js");
|
|
399
|
+
saveLinearConfig(newConfig);
|
|
400
|
+
|
|
401
|
+
const saved = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
402
|
+
expect(saved.apiKey).toBe("new_api_key");
|
|
403
|
+
expect(saved.teamId).toBe("NEW-TEAM");
|
|
404
|
+
expect(saved.projectId).toBe("new_proj");
|
|
405
|
+
expect(saved.defaultLabels.prd).toBe("custom-prd");
|
|
406
|
+
expect(saved.defaultLabels.epic).toBe("custom-epic");
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test("validates config before saving", async () => {
|
|
410
|
+
const invalidConfig = {
|
|
411
|
+
apiKey: "lin_api_test",
|
|
412
|
+
// missing teamId and projectId
|
|
413
|
+
} as any;
|
|
414
|
+
|
|
415
|
+
const { saveLinearConfig } = await import("../linear/config.js");
|
|
416
|
+
|
|
417
|
+
expect(() => saveLinearConfig(invalidConfig)).toThrow(
|
|
418
|
+
"Invalid Linear config: teamId is required",
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
// Ensure file was not created
|
|
422
|
+
expect(existsSync(CONFIG_PATH)).toBe(false);
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
});
|