Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitd84ee78

Browse files
committed
more test cleanup
1 parent89ac9ee commitd84ee78

File tree

11 files changed

+1354
-595
lines changed

11 files changed

+1354
-595
lines changed

‎.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@
66
/coverage/
77
*.vsix
88
yarn-error.log
9+
/reports/
10+
/.stryker-tmp/
11+
stryker.log

‎CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
yarn lint:fix# Lint with auto-fix
1111
yarn test:ci --coverage# Run ALL unit tests (ALWAYS use this)
1212
yarn pretest&& yarn test:integration# Integration tests
13+
yarn mutate# Mutation testing (may take up to 180s - run occasionally)
1314
```
1415

1516
##Key Rules

‎package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"fmt":"prettier --write .",
2323
"lint":"eslint . --ext ts,md,json",
2424
"lint:fix":"yarn lint --fix",
25+
"mutate":"npx stryker run",
2526
"package":"webpack --mode production --devtool hidden-source-map",
2627
"package:prerelease":"npx vsce package --pre-release",
2728
"pretest":"tsc -p . --outDir out && yarn run build && yarn run lint",
@@ -307,6 +308,8 @@
307308
"zod":"^3.25.65"
308309
},
309310
"devDependencies": {
311+
"@stryker-mutator/core":"^9.0.1",
312+
"@stryker-mutator/vitest-runner":"^9.0.1",
310313
"@types/eventsource":"^3.0.0",
311314
"@types/glob":"^7.1.3",
312315
"@types/node":"^22.14.1",
@@ -316,7 +319,7 @@
316319
"@types/ws":"^8.18.1",
317320
"@typescript-eslint/eslint-plugin":"^7.0.0",
318321
"@typescript-eslint/parser":"^6.21.0",
319-
"@vitest/coverage-v8":"0.34.6",
322+
"@vitest/coverage-v8":"^2.1.0",
320323
"@vscode/test-cli":"^0.0.10",
321324
"@vscode/test-electron":"^2.5.2",
322325
"@vscode/vsce":"^2.21.1",
@@ -338,7 +341,7 @@
338341
"tsc-watch":"^6.2.1",
339342
"typescript":"^5.4.5",
340343
"utf-8-validate":"^6.0.5",
341-
"vitest":"^0.34.6",
344+
"vitest":"^2.1.0",
342345
"vscode-test":"^1.5.0",
343346
"webpack":"^5.99.6",
344347
"webpack-cli":"^5.1.4"

‎src/api-helper.test.ts

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ErrorEvent } from "eventsource";
22
import{describe,expect,it}from"vitest";
33
import{
44
AgentMetadataEventSchema,
5+
AgentMetadataEventSchemaArray,
56
errToStr,
67
extractAgents,
78
extractAllAgents,
@@ -70,6 +71,29 @@ describe("api-helper", () => {
7071
consterrorEvent=newErrorEvent("error",eventInit);
7172
expect(errToStr(errorEvent,"default")).toBe(expected);
7273
});
74+
75+
it("should handle API error response",()=>{
76+
constapiError={
77+
isAxiosError:true,
78+
response:{
79+
data:{
80+
message:"API request failed",
81+
detail:"API request failed",
82+
},
83+
},
84+
};
85+
expect(errToStr(apiError,"default")).toBe("API request failed");
86+
});
87+
88+
it("should handle API error response object",()=>{
89+
constapiErrorResponse={
90+
detail:"Invalid authentication",
91+
message:"Invalid authentication",
92+
};
93+
expect(errToStr(apiErrorResponse,"default")).toBe(
94+
"Invalid authentication",
95+
);
96+
});
7397
});
7498

7599
describe("extractAgents",()=>{
@@ -148,16 +172,73 @@ describe("api-helper", () => {
148172

149173
describe("AgentMetadataEventSchema",()=>{
150174
it("should validate correct event",()=>{
151-
constresult=AgentMetadataEventSchema.safeParse(
152-
createValidMetadataEvent(),
153-
);
175+
constvalidEvent=createValidMetadataEvent();
176+
constresult=AgentMetadataEventSchema.safeParse(validEvent);
154177
expect(result.success).toBe(true);
178+
if(result.success){
179+
expect(result.data.result.collected_at).toBe("2024-01-01T00:00:00Z");
180+
expect(result.data.result.age).toBe(60);
181+
expect(result.data.result.value).toBe("test-value");
182+
expect(result.data.result.error).toBe("");
183+
expect(result.data.description.display_name).toBe("Test Metric");
184+
expect(result.data.description.key).toBe("test_metric");
185+
expect(result.data.description.script).toBe("echo 'test'");
186+
expect(result.data.description.interval).toBe(30);
187+
expect(result.data.description.timeout).toBe(10);
188+
}
155189
});
156190

157191
it("should reject invalid event",()=>{
158192
constevent=createValidMetadataEvent({age:"invalid"});
159193
constresult=AgentMetadataEventSchema.safeParse(event);
160194
expect(result.success).toBe(false);
195+
if(!result.success){
196+
expect(result.error.issues[0].code).toBe("invalid_type");
197+
expect(result.error.issues[0].path).toEqual(["result","age"]);
198+
}
199+
});
200+
201+
it("should validate array of events",()=>{
202+
constevents=[
203+
createValidMetadataEvent(),
204+
createValidMetadataEvent({value:"different-value"}),
205+
];
206+
constresult=AgentMetadataEventSchemaArray.safeParse(events);
207+
expect(result.success).toBe(true);
208+
if(result.success){
209+
expect(result.data).toHaveLength(2);
210+
expect(result.data[0].result.value).toBe("test-value");
211+
expect(result.data[1].result.value).toBe("different-value");
212+
}
213+
});
214+
215+
it("should reject array with invalid events",()=>{
216+
constevents=[createValidMetadataEvent(),{invalid:"structure"}];
217+
constresult=AgentMetadataEventSchemaArray.safeParse(events);
218+
expect(result.success).toBe(false);
219+
});
220+
221+
it("should handle missing required fields",()=>{
222+
constincompleteEvent={
223+
result:{
224+
collected_at:"2024-01-01T00:00:00Z",
225+
// missing age, value, error
226+
},
227+
description:{
228+
display_name:"Test",
229+
// missing other fields
230+
},
231+
};
232+
constresult=AgentMetadataEventSchema.safeParse(incompleteEvent);
233+
expect(result.success).toBe(false);
234+
if(!result.success){
235+
constmissingFields=result.error.issues.map(
236+
(issue)=>issue.path[issue.path.length-1],
237+
);
238+
expect(missingFields).toContain("age");
239+
expect(missingFields).toContain("value");
240+
expect(missingFields).toContain("error");
241+
}
161242
});
162243
});
163244
});

‎src/api.test.ts

Lines changed: 199 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,22 @@ describe("api", () => {
104104
false,
105105
],
106106
[
107-
"should handle null/undefined config values",
107+
"should handle null config values",
108108
{"coder.tlsCertFile":null,"coder.tlsKeyFile":null},
109109
true,
110110
],
111+
[
112+
"should handle undefined config values",
113+
{"coder.tlsCertFile":undefined,"coder.tlsKeyFile":undefined},
114+
true,
115+
],
116+
["should handle missing config entries",{},true],
111117
])("%s",(_,configValues:Record<string,unknown>,expected)=>{
112118
mockConfiguration.get.mockImplementation((key:string)=>{
113-
returnconfigValues[key]??"";
119+
if(keyinconfigValues){
120+
returnconfigValues[key];
121+
}
122+
returnundefined;
114123
});
115124

116125
// Mock expandPath to return the path as-is
@@ -173,12 +182,32 @@ describe("api", () => {
173182
rejectUnauthorized:true,
174183
},
175184
],
185+
[
186+
"undefined configuration values",
187+
{
188+
"coder.tlsCertFile":undefined,
189+
"coder.tlsKeyFile":undefined,
190+
"coder.tlsCaFile":undefined,
191+
"coder.tlsAltHost":undefined,
192+
"coder.insecure":undefined,
193+
},
194+
{
195+
cert:undefined,
196+
key:undefined,
197+
ca:undefined,
198+
servername:undefined,
199+
rejectUnauthorized:true,
200+
},
201+
],
176202
])(
177203
"should create ProxyAgent with %s",
178204
async(_,configValues:Record<string,unknown>,expectedAgentConfig)=>{
179-
mockConfiguration.get.mockImplementation(
180-
(key:string)=>configValues[key]??"",
181-
);
205+
mockConfiguration.get.mockImplementation((key:string)=>{
206+
if(keyinconfigValues){
207+
returnconfigValues[key];
208+
}
209+
returnundefined;
210+
});
182211

183212
if(configValues["coder.tlsCertFile"]){
184213
vi.mocked(fs.readFile)
@@ -374,6 +403,171 @@ describe("api", () => {
374403
expect.objectContaining({url:"https://example.com/api"}),
375404
);
376405
});
406+
407+
it("should handle stream data events",async()=>{
408+
letdataHandler:(chunk:Buffer)=>void;
409+
constmockData={
410+
on:vi.fn((event:string,handler:(chunk:Buffer)=>void)=>{
411+
if(event==="data"){
412+
dataHandler=handler;
413+
}
414+
}),
415+
destroy:vi.fn(),
416+
};
417+
418+
constmockAxiosInstance={
419+
request:vi
420+
.fn()
421+
.mockResolvedValue(createMockAxiosResponse({data:mockData})),
422+
};
423+
424+
constadapter=createStreamingFetchAdapter(mockAxiosInstanceasnever);
425+
426+
letenqueuedData:Buffer|undefined;
427+
global.ReadableStream=vi.fn().mockImplementation((options)=>{
428+
constcontroller={
429+
enqueue:vi.fn((chunk:Buffer)=>{
430+
enqueuedData=chunk;
431+
}),
432+
close:vi.fn(),
433+
error:vi.fn(),
434+
};
435+
if(options.start){
436+
options.start(controller);
437+
}
438+
return{getReader:vi.fn(()=>({read:vi.fn()}))};
439+
})asnever;
440+
441+
awaitadapter("https://example.com/api");
442+
443+
// Simulate data event
444+
consttestData=Buffer.from("test data");
445+
dataHandler!(testData);
446+
447+
expect(enqueuedData).toEqual(testData);
448+
expect(mockData.on).toHaveBeenCalledWith("data",expect.any(Function));
449+
});
450+
451+
it("should handle stream end event",async()=>{
452+
letendHandler:()=>void;
453+
constmockData={
454+
on:vi.fn((event:string,handler:()=>void)=>{
455+
if(event==="end"){
456+
endHandler=handler;
457+
}
458+
}),
459+
destroy:vi.fn(),
460+
};
461+
462+
constmockAxiosInstance={
463+
request:vi
464+
.fn()
465+
.mockResolvedValue(createMockAxiosResponse({data:mockData})),
466+
};
467+
468+
constadapter=createStreamingFetchAdapter(mockAxiosInstanceasnever);
469+
470+
letstreamClosed=false;
471+
global.ReadableStream=vi.fn().mockImplementation((options)=>{
472+
constcontroller={
473+
enqueue:vi.fn(),
474+
close:vi.fn(()=>{
475+
streamClosed=true;
476+
}),
477+
error:vi.fn(),
478+
};
479+
if(options.start){
480+
options.start(controller);
481+
}
482+
return{getReader:vi.fn(()=>({read:vi.fn()}))};
483+
})asnever;
484+
485+
awaitadapter("https://example.com/api");
486+
487+
// Simulate end event
488+
endHandler!();
489+
490+
expect(streamClosed).toBe(true);
491+
expect(mockData.on).toHaveBeenCalledWith("end",expect.any(Function));
492+
});
493+
494+
it("should handle stream error event",async()=>{
495+
leterrorHandler:(err:Error)=>void;
496+
constmockData={
497+
on:vi.fn((event:string,handler:(err:Error)=>void)=>{
498+
if(event==="error"){
499+
errorHandler=handler;
500+
}
501+
}),
502+
destroy:vi.fn(),
503+
};
504+
505+
constmockAxiosInstance={
506+
request:vi
507+
.fn()
508+
.mockResolvedValue(createMockAxiosResponse({data:mockData})),
509+
};
510+
511+
constadapter=createStreamingFetchAdapter(mockAxiosInstanceasnever);
512+
513+
letstreamError:Error|undefined;
514+
global.ReadableStream=vi.fn().mockImplementation((options)=>{
515+
constcontroller={
516+
enqueue:vi.fn(),
517+
close:vi.fn(),
518+
error:vi.fn((err:Error)=>{
519+
streamError=err;
520+
}),
521+
};
522+
if(options.start){
523+
options.start(controller);
524+
}
525+
return{getReader:vi.fn(()=>({read:vi.fn()}))};
526+
})asnever;
527+
528+
awaitadapter("https://example.com/api");
529+
530+
// Simulate error event
531+
consttestError=newError("Stream error");
532+
errorHandler!(testError);
533+
534+
expect(streamError).toBe(testError);
535+
expect(mockData.on).toHaveBeenCalledWith("error",expect.any(Function));
536+
});
537+
538+
it("should handle stream cancel",async()=>{
539+
constmockData={
540+
on:vi.fn(),
541+
destroy:vi.fn(),
542+
};
543+
544+
constmockAxiosInstance={
545+
request:vi
546+
.fn()
547+
.mockResolvedValue(createMockAxiosResponse({data:mockData})),
548+
};
549+
550+
constadapter=createStreamingFetchAdapter(mockAxiosInstanceasnever);
551+
552+
letcancelFunction:(()=>Promise<void>)|undefined;
553+
global.ReadableStream=vi.fn().mockImplementation((options)=>{
554+
if(options.cancel){
555+
cancelFunction=options.cancel;
556+
}
557+
if(options.start){
558+
options.start({enqueue:vi.fn(),close:vi.fn(),error:vi.fn()});
559+
}
560+
return{getReader:vi.fn(()=>({read:vi.fn()}))};
561+
})asnever;
562+
563+
awaitadapter("https://example.com/api");
564+
565+
// Call cancel
566+
expect(cancelFunction).toBeDefined();
567+
awaitcancelFunction!();
568+
569+
expect(mockData.destroy).toHaveBeenCalled();
570+
});
377571
});
378572

379573
describe("startWorkspaceIfStoppedOrFailed",()=>{

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp