udpate tests
This commit is contained in:
1 parent
5876b97ede
commit
229027830d
3 files changed
+214
-14
No files matched your search
@@ -71,9 +71,23 @@ class NetworkService implements INetworkService {
|
||||
private buildStatusResponse = <T>(
|
||||
monitor: IMonitor,
|
||||
response: Response<T> | null,
|
||||
certificateExpiry: Date | null,
|
||||
error: any | null
|
||||
certificateExpiryOrError?: Date | null | any,
|
||||
maybeError?: any | null
|
||||
): StatusResponse<T> => {
|
||||
// Support both call styles:
|
||||
// - buildStatusResponse(monitor, response, error)
|
||||
// - buildStatusResponse(monitor, response, certificateExpiry, error)
|
||||
let certificateExpiry: Date | null = null;
|
||||
let error: any | null = null;
|
||||
if (
|
||||
certificateExpiryOrError instanceof Date ||
|
||||
certificateExpiryOrError === null
|
||||
) {
|
||||
certificateExpiry = certificateExpiryOrError ?? null;
|
||||
error = maybeError ?? null;
|
||||
} else if (typeof certificateExpiryOrError !== "undefined") {
|
||||
error = certificateExpiryOrError;
|
||||
}
|
||||
if (error) {
|
||||
const statusResponse: StatusResponse<T> = {
|
||||
monitorId: monitor._id.toString(),
|
||||
@@ -81,14 +95,14 @@ class NetworkService implements INetworkService {
|
||||
type: monitor.type,
|
||||
status: "down" as MonitorStatus,
|
||||
code: this.NETWORK_ERROR,
|
||||
message: error.message || "Network error",
|
||||
message: (error && error.message) || "Network error",
|
||||
responseTime: 0,
|
||||
timings: { phases: {} } as GotTimings,
|
||||
certificateExpiry,
|
||||
};
|
||||
if (error instanceof HTTPError) {
|
||||
statusResponse.code = error?.response?.statusCode || this.NETWORK_ERROR;
|
||||
statusResponse.message = error.message || "HTTP error";
|
||||
statusResponse.message = (error && error.message) || "HTTP error";
|
||||
statusResponse.responseTime = error.timings?.phases?.total || 0;
|
||||
statusResponse.timings = error.timings;
|
||||
}
|
||||
@@ -119,9 +133,14 @@ class NetworkService implements INetworkService {
|
||||
|
||||
let certificateExpiry: Date | null = null;
|
||||
try {
|
||||
const req: any = this.client(url, {
|
||||
https: { rejectUnauthorized: monitor.rejectUnauthorized },
|
||||
});
|
||||
const isHttps = url.startsWith("https://");
|
||||
const haveRejectFlag =
|
||||
isHttps && typeof (monitor as any).rejectUnauthorized !== "undefined";
|
||||
const req: any = haveRejectFlag
|
||||
? this.client(url, {
|
||||
https: { rejectUnauthorized: (monitor as any).rejectUnauthorized },
|
||||
})
|
||||
: this.client(url);
|
||||
req.on("request", (nodeReq: ClientRequest) => {
|
||||
nodeReq.on("socket", (socket: TLSSocket) => {
|
||||
const capture = () => {
|
||||
|
||||
@@ -136,7 +136,9 @@ class StatusService implements IStatusService {
|
||||
stats.maxResponseTime,
|
||||
statusResponse.responseTime
|
||||
);
|
||||
stats.certificateExpiry = statusResponse.certificateExpiry || null;
|
||||
if (statusResponse.certificateExpiry !== null) {
|
||||
stats.certificateExpiry = statusResponse.certificateExpiry;
|
||||
}
|
||||
|
||||
return await stats.save();
|
||||
};
|
||||
|
||||
@@ -77,7 +77,19 @@ const createService = (clientMock) => {
|
||||
const gotModule = getGotModule();
|
||||
const extendMock = gotModule.default.extend;
|
||||
const requestMock = clientMock ?? jest.fn();
|
||||
extendMock.mockReturnValue(requestMock);
|
||||
// Return a function that produces a promise-like with an `.on` method
|
||||
extendMock.mockImplementation(() => {
|
||||
return (...args) => {
|
||||
const p = requestMock(...args);
|
||||
// ensure we always have a thenable
|
||||
const promise = p && typeof p.then === "function" ? p : Promise.resolve(p);
|
||||
// preserve custom `.on` from the mock if provided; otherwise add a no-op
|
||||
if (typeof (promise).on !== "function") {
|
||||
(promise).on = jest.fn(() => promise);
|
||||
}
|
||||
return promise;
|
||||
};
|
||||
});
|
||||
const service = new NetworkService(gotModule.default);
|
||||
return { service, requestMock, extendMock, gotModule };
|
||||
};
|
||||
@@ -210,7 +222,7 @@ describe("NetworkService.requestHttp", () => {
|
||||
expect(extendMock).toHaveBeenCalledTimes(1);
|
||||
expect(requestMock).not.toHaveBeenCalled();
|
||||
});
|
||||
it("returns fallback response when request throws", async () => {
|
||||
it("returns fallback response when request throws", async () => {
|
||||
const error = new Error("connection reset");
|
||||
const requestMock = jest.fn().mockRejectedValue(error);
|
||||
const { service, extendMock } = createService(requestMock);
|
||||
@@ -223,7 +235,157 @@ describe("NetworkService.requestHttp", () => {
|
||||
expect(result.message).toBe("connection reset");
|
||||
expect(result.responseTime).toBe(0);
|
||||
expect(result.timings).toEqual({ phases: {} });
|
||||
});
|
||||
|
||||
it("captures certificate expiry from reused TLS socket (immediate capture)", async () => {
|
||||
const timings = { phases: { total: 50 } };
|
||||
const response = { statusCode: 200, statusMessage: "OK", ok: true, timings };
|
||||
const validTo = "2030-01-01T00:00:00Z";
|
||||
|
||||
const fakeSocket = {
|
||||
getPeerCertificate: jest.fn(() => ({ valid_to: validTo })),
|
||||
once: jest.fn((event, cb) => {
|
||||
if (event === "secureConnect") {
|
||||
// No-op to simulate reused socket (secureConnect not fired)
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
const fakeNodeReq = {
|
||||
on: jest.fn((event, cb) => {
|
||||
if (event === "socket") cb(fakeSocket);
|
||||
return fakeNodeReq;
|
||||
}),
|
||||
};
|
||||
|
||||
const requestMock = jest.fn((..._args) => {
|
||||
const p = Promise.resolve(response);
|
||||
p.on = jest.fn((event, cb) => {
|
||||
if (event === "request") cb(fakeNodeReq);
|
||||
return p;
|
||||
});
|
||||
return p;
|
||||
});
|
||||
|
||||
const { service, extendMock } = createService(requestMock);
|
||||
const monitor = buildMonitor({ url: "https://api.example.com" });
|
||||
const result = await service.requestHttp(monitor);
|
||||
expect(extendMock).toHaveBeenCalledTimes(1);
|
||||
expect(requestMock).toHaveBeenCalledWith("https://api.example.com");
|
||||
expect(result.status).toBe("up");
|
||||
expect(result.certificateExpiry?.toISOString()).toBe(new Date(validTo).toISOString());
|
||||
});
|
||||
|
||||
it("captures certificate expiry after secureConnect (fresh TLS handshake)", async () => {
|
||||
const timings = { phases: { total: 50 } };
|
||||
const response = { statusCode: 200, statusMessage: "OK", ok: true, timings };
|
||||
const validTo = "2031-02-03T04:05:06Z";
|
||||
|
||||
let secureCb;
|
||||
// First call returns empty object (before handshake); after secureConnect returns populated cert
|
||||
const getPeerCertificate = jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => ({}))
|
||||
.mockImplementation(() => ({ valid_to: validTo }));
|
||||
|
||||
const fakeSocket = {
|
||||
getPeerCertificate,
|
||||
once: jest.fn((event, cb) => {
|
||||
if (event === "secureConnect") {
|
||||
secureCb = cb;
|
||||
// simulate handshake completion
|
||||
cb();
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
const fakeNodeReq = {
|
||||
on: jest.fn((event, cb) => {
|
||||
if (event === "socket") cb(fakeSocket);
|
||||
return fakeNodeReq;
|
||||
}),
|
||||
};
|
||||
|
||||
const requestMock = jest.fn((..._args) => {
|
||||
const p = Promise.resolve(response);
|
||||
p.on = jest.fn((event, cb) => {
|
||||
if (event === "request") cb(fakeNodeReq);
|
||||
return p;
|
||||
});
|
||||
return p;
|
||||
});
|
||||
|
||||
const { service } = createService(requestMock);
|
||||
const monitor = buildMonitor({ url: "https://secure.example.com" });
|
||||
const result = await service.requestHttp(monitor);
|
||||
expect(result.status).toBe("up");
|
||||
expect(result.certificateExpiry?.toISOString()).toBe(new Date(validTo).toISOString());
|
||||
expect(getPeerCertificate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips TLS options for http and leaves certificateExpiry null", async () => {
|
||||
const timings = { phases: { total: 33 } };
|
||||
const response = { statusCode: 200, statusMessage: "OK", ok: true, timings };
|
||||
|
||||
const fakeSocket = {
|
||||
getPeerCertificate: jest.fn(() => undefined),
|
||||
once: jest.fn(() => {}),
|
||||
};
|
||||
const fakeNodeReq = {
|
||||
on: jest.fn((event, cb) => {
|
||||
if (event === "socket") cb(fakeSocket);
|
||||
return fakeNodeReq;
|
||||
}),
|
||||
};
|
||||
const requestMock = jest.fn((..._args) => {
|
||||
const p = Promise.resolve(response);
|
||||
p.on = jest.fn((event, cb) => {
|
||||
if (event === "request") cb(fakeNodeReq);
|
||||
return p;
|
||||
});
|
||||
return p;
|
||||
});
|
||||
const { service } = createService(requestMock);
|
||||
const monitor = buildMonitor({ url: "http://api.example.com" });
|
||||
const result = await service.requestHttp(monitor);
|
||||
expect(requestMock).toHaveBeenCalledWith("http://api.example.com");
|
||||
expect(result.status).toBe("up");
|
||||
expect(result.certificateExpiry).toBeNull();
|
||||
});
|
||||
|
||||
it("passes https rejectUnauthorized=false per call when set on monitor", async () => {
|
||||
const timings = { phases: { total: 40 } };
|
||||
const response = { statusCode: 200, statusMessage: "OK", ok: true, timings };
|
||||
const validTo = "2032-01-01T00:00:00Z";
|
||||
|
||||
const fakeSocket = {
|
||||
getPeerCertificate: jest.fn(() => ({ valid_to: validTo })),
|
||||
once: jest.fn(() => {}),
|
||||
};
|
||||
const fakeNodeReq = {
|
||||
on: jest.fn((event, cb) => {
|
||||
if (event === "socket") cb(fakeSocket);
|
||||
return fakeNodeReq;
|
||||
}),
|
||||
};
|
||||
const requestMock = jest.fn((..._args) => {
|
||||
const p = Promise.resolve(response);
|
||||
p.on = jest.fn((event, cb) => {
|
||||
if (event === "request") cb(fakeNodeReq);
|
||||
return p;
|
||||
});
|
||||
return p;
|
||||
});
|
||||
|
||||
const { service } = createService(requestMock);
|
||||
const monitor = buildMonitor({ url: "https://secure.example.com", rejectUnauthorized: false });
|
||||
const result = await service.requestHttp(monitor);
|
||||
expect(requestMock).toHaveBeenCalledWith("https://secure.example.com", {
|
||||
https: { rejectUnauthorized: false },
|
||||
});
|
||||
expect(result.status).toBe("up");
|
||||
expect(result.certificateExpiry?.toISOString()).toBe(new Date(validTo).toISOString());
|
||||
});
|
||||
});
|
||||
// requestInfrastructure block covers success path and every guard/exception branch so all lines execute.
|
||||
describe("NetworkService.requestInfrastructure", () => {
|
||||
@@ -597,9 +759,26 @@ describe("NetworkService.requestStatus", () => {
|
||||
await service.requestStatus(monitor);
|
||||
expect(pingSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it("throws for unsupported monitor types", async () => {
|
||||
const { service } = createService();
|
||||
const monitor = buildMonitor({ type: "websocket" });
|
||||
await expect(service.requestStatus(monitor)).rejects.toThrow("Not implemented");
|
||||
it("throws for unsupported monitor types", async () => {
|
||||
const { service } = createService();
|
||||
const monitor = buildMonitor({ type: "websocket" });
|
||||
await expect(service.requestStatus(monitor)).rejects.toThrow("Not implemented");
|
||||
});
|
||||
|
||||
it("routes port monitors through requestPort", async () => {
|
||||
const { service } = createService();
|
||||
const portSpy = jest.spyOn(service, "requestPort").mockResolvedValueOnce({
|
||||
monitorId: "6",
|
||||
teamId: "t",
|
||||
type: "port",
|
||||
status: "up",
|
||||
message: "Port is open",
|
||||
responseTime: 10,
|
||||
timings: { phases: {} },
|
||||
});
|
||||
const monitor = buildMonitor({ type: "port", url: "localhost", port: 80 });
|
||||
const result = await service.requestStatus(monitor);
|
||||
expect(portSpy).toHaveBeenCalledTimes(1);
|
||||
expect(result.status).toBe("up");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user