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

Commit360b5c9

Browse files
committed
Add tests
1 parent485b5ae commit360b5c9

File tree

9 files changed

+427
-17
lines changed

9 files changed

+427
-17
lines changed

‎src/logging/formatters.ts‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import prettyBytes from "pretty-bytes";
33

44
import{sizeOf}from"./utils";
55

6-
importtype{InternalAxiosRequestConfig}from"axios";
6+
importtype{AxiosRequestConfig}from"axios";
77

88
constSENSITIVE_HEADERS=["Coder-Session-Token","Proxy-Authorization"];
99

@@ -21,7 +21,7 @@ export function formatTime(ms: number): string {
2121
}
2222

2323
exportfunctionformatMethod(method:string|undefined):string{
24-
return(method??"GET").toUpperCase();
24+
return(method ?method :"GET").toUpperCase();
2525
}
2626

2727
/**
@@ -55,9 +55,7 @@ export function formatContentLength(
5555
return"(? B)";
5656
}
5757

58-
exportfunctionformatUri(
59-
config:InternalAxiosRequestConfig|undefined,
60-
):string{
58+
exportfunctionformatUri(config:AxiosRequestConfig|undefined):string{
6159
returnconfig?.url||"<no url>";
6260
}
6361

‎src/logging/utils.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ export function shortId(id: string): string {
55
returnid.slice(0,8);
66
}
77

8+
/**
9+
* Returns the byte size of the data if it can be determined from the data's intrinsic properties,
10+
* otherwise returns undefined (e.g., for plain objects and arrays that would require serialization).
11+
*/
812
exportfunctionsizeOf(data:unknown):number|undefined{
913
if(data===null||data===undefined){
1014
return0;

‎src/logging/wsLogger.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,6 @@ export class WsLogger {
7777

7878
privateformatBytes():string{
7979
constbytes=prettyBytes(this.byteCount);
80-
returnthis.unknownByteCount ?`>=${bytes}` :bytes;
80+
returnthis.unknownByteCount ?`>=${bytes}` :bytes;
8181
}
8282
}

‎test/mocks/testHelpers.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import{vi}from"vitest";
22
import*asvscodefrom"vscode";
33

4+
import{typeLogger}from"@/logging/logger";
5+
46
/**
57
* Mock configuration provider that integrates with the vscode workspace configuration mock.
68
* Use this to set configuration values that will be returned by vscode.workspace.getConfiguration().
@@ -286,3 +288,13 @@ export class InMemorySecretStorage implements vscode.SecretStorage {
286288
this.listeners.forEach((listener)=>listener(event));
287289
}
288290
}
291+
292+
exportfunctioncreateMockLogger():Logger{
293+
return{
294+
trace:vi.fn(),
295+
debug:vi.fn(),
296+
info:vi.fn(),
297+
warn:vi.fn(),
298+
error:vi.fn(),
299+
};
300+
}

‎test/unit/core/cliManager.test.ts‎

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import * as vscode from "vscode";
1212
import{CliManager}from"@/core/cliManager";
1313
import*ascliUtilsfrom"@/core/cliUtils";
1414
import{PathResolver}from"@/core/pathResolver";
15-
import{typeLogger}from"@/logging/logger";
1615
import*aspgpfrom"@/pgp";
1716

1817
import{
18+
createMockLogger,
1919
MockConfigurationProvider,
2020
MockProgressReporter,
2121
MockUserInteraction,
@@ -625,16 +625,6 @@ describe("CliManager", () => {
625625
});
626626
});
627627

628-
functioncreateMockLogger():Logger{
629-
return{
630-
trace:vi.fn(),
631-
debug:vi.fn(),
632-
info:vi.fn(),
633-
warn:vi.fn(),
634-
error:vi.fn(),
635-
};
636-
}
637-
638628
functioncreateMockApi(version:string,url:string):Api{
639629
constaxios={
640630
defaults:{baseURL:url},
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import{describe,expect,it}from"vitest";
2+
3+
import{
4+
formatBody,
5+
formatContentLength,
6+
formatHeaders,
7+
formatMethod,
8+
formatTime,
9+
formatUri,
10+
}from"@/logging/formatters";
11+
12+
describe("Logging formatters",()=>{
13+
it("formats time in appropriate units",()=>{
14+
expect(formatTime(500)).toBe("500ms");
15+
expect(formatTime(1000)).toBe("1.00s");
16+
expect(formatTime(5500)).toBe("5.50s");
17+
expect(formatTime(60000)).toBe("1.00m");
18+
expect(formatTime(150000)).toBe("2.50m");
19+
expect(formatTime(3600000)).toBe("1.00h");
20+
expect(formatTime(7255000)).toBe("2.02h");
21+
});
22+
23+
describe("formatMethod",()=>{
24+
it("normalizes HTTP methods to uppercase",()=>{
25+
expect(formatMethod("get")).toBe("GET");
26+
expect(formatMethod("post")).toBe("POST");
27+
expect(formatMethod("PUT")).toBe("PUT");
28+
expect(formatMethod("delete")).toBe("DELETE");
29+
});
30+
31+
it("defaults to GET for falsy values",()=>{
32+
expect(formatMethod(undefined)).toBe("GET");
33+
expect(formatMethod("")).toBe("GET");
34+
});
35+
});
36+
37+
describe("formatContentLength",()=>{
38+
it("uses content-length header when available",()=>{
39+
constresult=formatContentLength({"content-length":"1024"},null);
40+
expect(result).toContain("1.02 kB");
41+
});
42+
43+
it("handles invalid content-length header",()=>{
44+
constresult=formatContentLength({"content-length":"invalid"},null);
45+
expect(result).toContain("?");
46+
});
47+
48+
it.each([
49+
["hello",5],
50+
[Buffer.from("test"),4],
51+
[123,8],
52+
[false,8],
53+
[BigInt(1234),4],
54+
[null,0],
55+
[undefined,0],
56+
])("calculates size for %s",(data:unknown,bytes:number)=>{
57+
constresult=formatContentLength({},data);
58+
expect(result).toContain(`${bytes} B`);
59+
});
60+
61+
it("estimates size for objects",()=>{
62+
constresult=formatContentLength({},{foo:"bar"});
63+
expect(result).toMatch(/~\d+/);
64+
});
65+
66+
it("handles circular references safely",()=>{
67+
constcircular:Record<string,unknown>={a:1};
68+
circular.self=circular;
69+
constresult=formatContentLength({},circular);
70+
expect(result).toMatch(/~\d+/);
71+
});
72+
});
73+
74+
describe("formatUri",()=>{
75+
it("returns URL when present",()=>{
76+
expect(formatUri({url:"https://example.com/api"})).toBe(
77+
"https://example.com/api",
78+
);
79+
expect(formatUri({url:"/relative/path"})).toBe("/relative/path");
80+
});
81+
82+
it("returns placeholder for missing URL",()=>{
83+
expect(formatUri(undefined)).toContain("no url");
84+
expect(formatUri({})).toContain("no url");
85+
expect(formatUri({url:""})).toContain("no url");
86+
});
87+
});
88+
89+
describe("formatHeaders",()=>{
90+
it("formats headers as key-value pairs",()=>{
91+
constheaders={
92+
"content-type":"application/json",
93+
accept:"text/html",
94+
};
95+
constresult=formatHeaders(headers);
96+
expect(result).toContain("content-type: application/json");
97+
expect(result).toContain("accept: text/html");
98+
});
99+
100+
it("redacts sensitive headers",()=>{
101+
constsensitiveHeaders=["Coder-Session-Token","Proxy-Authorization"];
102+
103+
sensitiveHeaders.forEach((header)=>{
104+
constresult=formatHeaders({[header]:"secret-value"});
105+
expect(result).toContain(`${header}: <redacted>`);
106+
expect(result).not.toContain("secret-value");
107+
});
108+
});
109+
110+
it("returns placeholder for empty headers",()=>{
111+
expect(formatHeaders({})).toBe("<no headers>");
112+
});
113+
});
114+
115+
describe("formatBody",()=>{
116+
it("formats various body types",()=>{
117+
expect(formatBody({key:"value"})).toContain("key: 'value'");
118+
expect(formatBody("plain text")).toContain("plain text");
119+
expect(formatBody([1,2,3])).toContain("1");
120+
expect(formatBody(123)).toContain("123");
121+
expect(formatBody(true)).toContain("true");
122+
});
123+
124+
it("handles circular references gracefully",()=>{
125+
constcircular:Record<string,unknown>={a:1};
126+
circular.self=circular;
127+
constresult=formatBody(circular);
128+
expect(result).toBeTruthy();
129+
expect(result).not.toContain("invalid body");
130+
expect(result).toContain("a: 1");
131+
});
132+
133+
it("handles deep nesting",()=>{
134+
constdeep={
135+
level1:{level2:{level3:{level4:{value:"deep"}}}},
136+
};
137+
constresult=formatBody(deep);
138+
expect(result).toContain("level4: { value: 'deep' }");
139+
});
140+
141+
it("returns placeholder for empty values",()=>{
142+
constemptyValues=[null,undefined,"",0,false];
143+
emptyValues.forEach((value)=>{
144+
expect(formatBody(value)).toContain("no body");
145+
});
146+
});
147+
});
148+
});
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import{AxiosError,typeAxiosHeaders,typeAxiosResponse}from"axios";
2+
import{describe,expect,it,vi}from"vitest";
3+
4+
import{
5+
createRequestMeta,
6+
logError,
7+
logRequest,
8+
logResponse,
9+
}from"@/logging/httpLogger";
10+
import{
11+
HttpClientLogLevel,
12+
typeRequestConfigWithMeta,
13+
}from"@/logging/types";
14+
15+
import{createMockLogger}from"../../mocks/testHelpers";
16+
17+
describe("REST HTTP Logger",()=>{
18+
describe("log level behavior",()=>{
19+
constconfig={
20+
method:"POST",
21+
url:"https://api.example.com/endpoint",
22+
headers:{
23+
"content-type":"application/json",
24+
}asunknownasAxiosHeaders,
25+
data:{key:"value"},
26+
metadata:createRequestMeta(),
27+
}asRequestConfigWithMeta;
28+
29+
it("respects NONE level for trace logs",()=>{
30+
constlogger=createMockLogger();
31+
32+
logRequest(logger,config,HttpClientLogLevel.NONE);
33+
logResponse(
34+
logger,
35+
{status:200}asAxiosResponse,
36+
HttpClientLogLevel.NONE,
37+
);
38+
logError(logger,newError("test"),HttpClientLogLevel.NONE);
39+
40+
expect(logger.trace).not.toHaveBeenCalled();
41+
expect(logger.error).toHaveBeenCalled();// always log errors
42+
});
43+
44+
it("includes headers at HEADERS level but not at BASIC",()=>{
45+
constlogger=createMockLogger();
46+
47+
logRequest(logger,config,HttpClientLogLevel.BASIC);
48+
expect(logger.trace).not.toHaveBeenCalledWith(
49+
expect.stringContaining("content-type"),
50+
);
51+
52+
vi.clearAllMocks();
53+
logRequest(logger,config,HttpClientLogLevel.HEADERS);
54+
expect(logger.trace).toHaveBeenCalledWith(
55+
expect.stringContaining("content-type"),
56+
);
57+
});
58+
59+
it("includes body at BODY level but not at HEADERS",()=>{
60+
constlogger=createMockLogger();
61+
62+
logRequest(logger,config,HttpClientLogLevel.HEADERS);
63+
expect(logger.trace).not.toHaveBeenCalledWith(
64+
expect.stringContaining("key: 'value'"),
65+
);
66+
67+
vi.clearAllMocks();
68+
logRequest(logger,config,HttpClientLogLevel.BODY);
69+
expect(logger.trace).toHaveBeenCalledWith(
70+
expect.stringContaining("key: 'value'"),
71+
);
72+
});
73+
});
74+
75+
describe("error handling",()=>{
76+
it("distinguishes between network errors and response errors",()=>{
77+
constlogger=createMockLogger();
78+
79+
constnetworkError=newAxiosError("Some Network Error","ECONNREFUSED");
80+
networkError.config={
81+
metadata:createRequestMeta(),
82+
}asRequestConfigWithMeta;
83+
84+
logError(logger,networkError,HttpClientLogLevel.BASIC);
85+
expect(logger.error).toHaveBeenCalledWith(
86+
expect.stringContaining("Some Network Error"),
87+
);
88+
89+
// Response error (4xx/5xx)
90+
vi.clearAllMocks();
91+
constresponseError=newAxiosError("Bad Request");
92+
responseError.config={
93+
metadata:createRequestMeta(),
94+
}asRequestConfigWithMeta;
95+
responseError.response={status:400}asAxiosResponse;
96+
97+
logError(logger,responseError,HttpClientLogLevel.BASIC);
98+
expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("400"));
99+
expect(logger.error).toHaveBeenCalledWith(
100+
expect.stringContaining("Bad Request"),
101+
);
102+
});
103+
104+
it("handles non-Axios errors",()=>{
105+
constlogger=createMockLogger();
106+
consterror=newError("Generic error");
107+
108+
logError(logger,error,HttpClientLogLevel.BASIC);
109+
expect(logger.error).toHaveBeenCalledWith("Request error",error);
110+
});
111+
});
112+
});

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp