@zuplo/cli 6.52.4 → 6.52.6
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/dist/__tests__/integration/delete.integration.test.d.ts +2 -0
- package/dist/__tests__/integration/delete.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/delete.integration.test.js +162 -0
- package/dist/__tests__/integration/delete.integration.test.js.map +1 -0
- package/dist/__tests__/integration/deploy.integration.test.d.ts +2 -0
- package/dist/__tests__/integration/deploy.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/deploy.integration.test.js +249 -0
- package/dist/__tests__/integration/deploy.integration.test.js.map +1 -0
- package/dist/__tests__/integration/jest-mocks-setup.d.ts +2 -0
- package/dist/__tests__/integration/jest-mocks-setup.d.ts.map +1 -0
- package/dist/__tests__/integration/jest-mocks-setup.js +59 -0
- package/dist/__tests__/integration/jest-mocks-setup.js.map +1 -0
- package/dist/__tests__/integration/jest-setup.d.ts +2 -0
- package/dist/__tests__/integration/jest-setup.d.ts.map +1 -0
- package/dist/__tests__/integration/jest-setup.js +12 -0
- package/dist/__tests__/integration/jest-setup.js.map +1 -0
- package/dist/__tests__/integration/link.integration.test.d.ts +2 -0
- package/dist/__tests__/integration/link.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/link.integration.test.js +340 -0
- package/dist/__tests__/integration/link.integration.test.js.map +1 -0
- package/dist/__tests__/integration/list.integration.test.d.ts +2 -0
- package/dist/__tests__/integration/list.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/list.integration.test.js +156 -0
- package/dist/__tests__/integration/list.integration.test.js.map +1 -0
- package/dist/__tests__/integration/test-utils.d.ts +30 -0
- package/dist/__tests__/integration/test-utils.d.ts.map +1 -0
- package/dist/__tests__/integration/test-utils.js +82 -0
- package/dist/__tests__/integration/test-utils.js.map +1 -0
- package/dist/__tests__/integration/tunnel.integration.test.d.ts +2 -0
- package/dist/__tests__/integration/tunnel.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/tunnel.integration.test.js +477 -0
- package/dist/__tests__/integration/tunnel.integration.test.js.map +1 -0
- package/dist/__tests__/integration/variable.integration.test.d.ts +2 -0
- package/dist/__tests__/integration/variable.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/variable.integration.test.js +258 -0
- package/dist/__tests__/integration/variable.integration.test.js.map +1 -0
- package/dist/build/handler.d.ts.map +1 -1
- package/dist/build/handler.js +8 -1
- package/dist/build/handler.js.map +1 -1
- package/dist/common/builders/authenticated-command-builder.d.ts +18 -0
- package/dist/common/builders/authenticated-command-builder.d.ts.map +1 -0
- package/dist/common/builders/authenticated-command-builder.js +87 -0
- package/dist/common/builders/authenticated-command-builder.js.map +1 -0
- package/dist/common/builders/authenticated-command-builder.spec.d.ts +2 -0
- package/dist/common/builders/authenticated-command-builder.spec.d.ts.map +1 -0
- package/dist/common/builders/authenticated-command-builder.spec.js +268 -0
- package/dist/common/builders/authenticated-command-builder.spec.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import nock from "nock";
|
|
2
|
+
import settings from "../../common/settings.js";
|
|
3
|
+
export const TEST_AUTH_TOKEN = "test-auth-token-123";
|
|
4
|
+
export const TEST_ACCOUNT_NAME = "test-account";
|
|
5
|
+
export const TEST_PROJECT_NAME = "test-project";
|
|
6
|
+
export const TEST_BRANCH_NAME = "main";
|
|
7
|
+
export const TEST_DEPLOYMENT_NAME = "test-deployment";
|
|
8
|
+
export const TEST_API_BASE = settings.ZUPLO_DEVELOPER_API_ENDPOINT;
|
|
9
|
+
export function setupAuthenticatedNock(scope) {
|
|
10
|
+
return scope.matchHeader("authorization", `Bearer ${TEST_AUTH_TOKEN}`);
|
|
11
|
+
}
|
|
12
|
+
export function setupTestEnvironment() {
|
|
13
|
+
process.env.ZUPLO_DEVELOPER_API_ENDPOINT =
|
|
14
|
+
settings.ZUPLO_DEVELOPER_API_ENDPOINT;
|
|
15
|
+
process.env.ZUPLO_API_KEY = TEST_AUTH_TOKEN;
|
|
16
|
+
setupAuthenticatedNock(nock(TEST_API_BASE))
|
|
17
|
+
.get("/v1/who-am-i")
|
|
18
|
+
.reply(200, { user: "test-user@example.com" })
|
|
19
|
+
.persist();
|
|
20
|
+
}
|
|
21
|
+
export function cleanupTest() {
|
|
22
|
+
nock.cleanAll();
|
|
23
|
+
delete process.env.ZUPLO_DEVELOPER_API_ENDPOINT;
|
|
24
|
+
delete process.env.ZUPLO_API_KEY;
|
|
25
|
+
}
|
|
26
|
+
export class RequestCapture {
|
|
27
|
+
requests = [];
|
|
28
|
+
capture(req, _interceptor, body) {
|
|
29
|
+
const captured = {
|
|
30
|
+
method: req.method || "GET",
|
|
31
|
+
path: req.path || "",
|
|
32
|
+
headers: this.normalizeHeaders(req.headers || {}),
|
|
33
|
+
};
|
|
34
|
+
if (body) {
|
|
35
|
+
if (typeof body === "string") {
|
|
36
|
+
try {
|
|
37
|
+
captured.body = JSON.parse(body);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
captured.body = "[FormData or non-JSON body]";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
captured.body = body;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
this.requests.push(captured);
|
|
48
|
+
}
|
|
49
|
+
getRequests() {
|
|
50
|
+
return [...this.requests];
|
|
51
|
+
}
|
|
52
|
+
clear() {
|
|
53
|
+
this.requests = [];
|
|
54
|
+
}
|
|
55
|
+
normalizeHeaders(headers) {
|
|
56
|
+
const normalized = {};
|
|
57
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
58
|
+
const normalizedKey = key.toLowerCase();
|
|
59
|
+
if (normalizedKey === "authorization") {
|
|
60
|
+
normalized[normalizedKey] = "[REDACTED]";
|
|
61
|
+
}
|
|
62
|
+
else if (normalizedKey === "user-agent") {
|
|
63
|
+
normalized[normalizedKey] = "[USER_AGENT]";
|
|
64
|
+
}
|
|
65
|
+
else if (normalizedKey === "content-length") {
|
|
66
|
+
normalized[normalizedKey] = "[CONTENT_LENGTH]";
|
|
67
|
+
}
|
|
68
|
+
else if (normalizedKey === "host") {
|
|
69
|
+
normalized[normalizedKey] = "[HOST]";
|
|
70
|
+
}
|
|
71
|
+
else if (normalizedKey === "content-type" &&
|
|
72
|
+
String(value).startsWith("multipart/form-data")) {
|
|
73
|
+
normalized[normalizedKey] = "multipart/form-data; boundary=[BOUNDARY]";
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
normalized[normalizedKey] = String(value);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return normalized;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=test-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-utils.js","sourceRoot":"","sources":["../../../src/__tests__/integration/test-utils.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Test utilities for CLI integration tests with nock interceptors\n */\nimport nock from \"nock\";\nimport settings from \"../../common/settings.js\";\n\n// Constants for testing\nexport const TEST_AUTH_TOKEN = \"test-auth-token-123\";\nexport const TEST_ACCOUNT_NAME = \"test-account\";\nexport const TEST_PROJECT_NAME = \"test-project\";\nexport const TEST_BRANCH_NAME = \"main\";\nexport const TEST_DEPLOYMENT_NAME = \"test-deployment\";\n\n// Base API endpoint for tests\nexport const TEST_API_BASE = settings.ZUPLO_DEVELOPER_API_ENDPOINT;\n\n/**\n * Common nock setup for authenticated requests\n */\nexport function setupAuthenticatedNock(scope: nock.Scope): nock.Scope {\n return scope.matchHeader(\"authorization\", `Bearer ${TEST_AUTH_TOKEN}`);\n}\n\n/**\n * Setup common environment variables for tests\n */\nexport function setupTestEnvironment() {\n // eslint-disable-next-line node/no-process-env\n process.env.ZUPLO_DEVELOPER_API_ENDPOINT =\n settings.ZUPLO_DEVELOPER_API_ENDPOINT;\n // eslint-disable-next-line node/no-process-env\n process.env.ZUPLO_API_KEY = TEST_AUTH_TOKEN;\n\n // Add a catch-all for any authentication validation calls\n setupAuthenticatedNock(nock(TEST_API_BASE))\n .get(\"/v1/who-am-i\")\n .reply(200, { user: \"test-user@example.com\" })\n .persist();\n}\n\n/**\n * Clean up environment and nock after tests\n */\nexport function cleanupTest() {\n nock.cleanAll();\n // eslint-disable-next-line node/no-process-env\n delete process.env.ZUPLO_DEVELOPER_API_ENDPOINT;\n // eslint-disable-next-line node/no-process-env\n delete process.env.ZUPLO_API_KEY;\n}\n\n/**\n * Helper to capture and normalize nock requests for snapshots\n */\nexport interface CapturedRequest {\n method: string;\n path: string;\n headers: Record<string, string>;\n body?: unknown;\n}\n\ninterface NockRequest {\n method?: string;\n path?: string;\n headers?: Record<string, unknown>;\n}\n\nexport class RequestCapture {\n private requests: CapturedRequest[] = [];\n\n capture(req: NockRequest, _interceptor?: unknown, body?: unknown): void {\n const captured: CapturedRequest = {\n method: req.method || \"GET\",\n path: req.path || \"\",\n headers: this.normalizeHeaders(req.headers || {}),\n };\n\n if (body) {\n if (typeof body === \"string\") {\n try {\n captured.body = JSON.parse(body);\n } catch {\n // If JSON parsing fails, store as string (e.g., FormData)\n captured.body = \"[FormData or non-JSON body]\";\n }\n } else {\n captured.body = body;\n }\n }\n\n this.requests.push(captured);\n }\n\n getRequests(): CapturedRequest[] {\n return [...this.requests];\n }\n\n clear(): void {\n this.requests = [];\n }\n\n private normalizeHeaders(\n headers: Record<string, unknown>\n ): Record<string, string> {\n const normalized: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(headers)) {\n // Normalize header names to lowercase and exclude dynamic values\n const normalizedKey = key.toLowerCase();\n\n if (normalizedKey === \"authorization\") {\n normalized[normalizedKey] = \"[REDACTED]\";\n } else if (normalizedKey === \"user-agent\") {\n normalized[normalizedKey] = \"[USER_AGENT]\";\n } else if (normalizedKey === \"content-length\") {\n normalized[normalizedKey] = \"[CONTENT_LENGTH]\";\n } else if (normalizedKey === \"host\") {\n normalized[normalizedKey] = \"[HOST]\";\n } else if (\n normalizedKey === \"content-type\" &&\n String(value).startsWith(\"multipart/form-data\")\n ) {\n normalized[normalizedKey] = \"multipart/form-data; boundary=[BOUNDARY]\";\n } else {\n normalized[normalizedKey] = String(value);\n }\n }\n\n return normalized;\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnel.integration.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/integration/tunnel.integration.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import nock from "nock";
|
|
2
|
+
import yargs from "yargs/yargs";
|
|
3
|
+
import tunnelCommand from "../../cmds/tunnel/index.js";
|
|
4
|
+
import { setupTestEnvironment, cleanupTest, setupAuthenticatedNock, RequestCapture, TEST_API_BASE, TEST_ACCOUNT_NAME, TEST_AUTH_TOKEN, } from "./test-utils.js";
|
|
5
|
+
async function executeTunnelCommand(subcommand, args) {
|
|
6
|
+
const yargsInstance = yargs([])
|
|
7
|
+
.command(tunnelCommand)
|
|
8
|
+
.help(false)
|
|
9
|
+
.version(false)
|
|
10
|
+
.exitProcess(false);
|
|
11
|
+
const commandArgs = ["tunnel", subcommand];
|
|
12
|
+
if (args.account) {
|
|
13
|
+
commandArgs.push("--account", args.account);
|
|
14
|
+
}
|
|
15
|
+
if (args["api-key"]) {
|
|
16
|
+
commandArgs.push("--api-key", args["api-key"]);
|
|
17
|
+
}
|
|
18
|
+
if (args["tunnel-name"]) {
|
|
19
|
+
commandArgs.push("--tunnel-name", args["tunnel-name"]);
|
|
20
|
+
}
|
|
21
|
+
if (args["tunnel-id"]) {
|
|
22
|
+
commandArgs.push("--tunnel-id", args["tunnel-id"]);
|
|
23
|
+
}
|
|
24
|
+
return await yargsInstance.parse(commandArgs);
|
|
25
|
+
}
|
|
26
|
+
async function executeTunnelServicesCommand(subcommand, args) {
|
|
27
|
+
const yargsInstance = yargs([])
|
|
28
|
+
.command(tunnelCommand)
|
|
29
|
+
.help(false)
|
|
30
|
+
.version(false)
|
|
31
|
+
.exitProcess(false);
|
|
32
|
+
const commandArgs = ["tunnel", "services", subcommand];
|
|
33
|
+
if (args.account) {
|
|
34
|
+
commandArgs.push("--account", args.account);
|
|
35
|
+
}
|
|
36
|
+
if (args["api-key"]) {
|
|
37
|
+
commandArgs.push("--api-key", args["api-key"]);
|
|
38
|
+
}
|
|
39
|
+
if (args["tunnel-id"]) {
|
|
40
|
+
commandArgs.push("--tunnel-id", args["tunnel-id"]);
|
|
41
|
+
}
|
|
42
|
+
if (args["configuration-file"]) {
|
|
43
|
+
commandArgs.push("--configuration-file", args["configuration-file"]);
|
|
44
|
+
}
|
|
45
|
+
return await yargsInstance.parse(commandArgs);
|
|
46
|
+
}
|
|
47
|
+
const mockPrintResult = jest.mocked(require("../../common/output.js").printResultToConsoleAndExitGracefully);
|
|
48
|
+
const mockPrintTable = jest.mocked(require("../../common/output.js").printTableToConsoleAndExitGracefully);
|
|
49
|
+
const mockPrintDiagnostics = jest.mocked(require("../../common/output.js").printDiagnosticsToConsole);
|
|
50
|
+
describe("Tunnel Command Integration Tests", () => {
|
|
51
|
+
let requestCapture;
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
setupTestEnvironment();
|
|
54
|
+
requestCapture = new RequestCapture();
|
|
55
|
+
nock.disableNetConnect();
|
|
56
|
+
jest.clearAllMocks();
|
|
57
|
+
});
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
cleanupTest();
|
|
60
|
+
requestCapture.clear();
|
|
61
|
+
});
|
|
62
|
+
describe("Tunnel Create Command", () => {
|
|
63
|
+
it("should intercept POST request to create tunnel", async () => {
|
|
64
|
+
const testTunnelName = "my-test-tunnel";
|
|
65
|
+
const mockResponse = {
|
|
66
|
+
id: "tunnel-123",
|
|
67
|
+
name: testTunnelName,
|
|
68
|
+
token: "tunnel-token-abc123",
|
|
69
|
+
};
|
|
70
|
+
const scope = setupAuthenticatedNock(nock(TEST_API_BASE))
|
|
71
|
+
.post(`/v1/accounts/${TEST_ACCOUNT_NAME}/tunnels`)
|
|
72
|
+
.reply(201, mockResponse);
|
|
73
|
+
scope.on("request", (req, interceptor, body) => {
|
|
74
|
+
requestCapture.capture(req, interceptor, body);
|
|
75
|
+
});
|
|
76
|
+
await executeTunnelCommand("create", {
|
|
77
|
+
account: TEST_ACCOUNT_NAME,
|
|
78
|
+
"api-key": TEST_AUTH_TOKEN,
|
|
79
|
+
"tunnel-name": testTunnelName,
|
|
80
|
+
});
|
|
81
|
+
expect(scope.isDone()).toBe(true);
|
|
82
|
+
expect(mockPrintTable).toHaveBeenCalledWith(mockResponse);
|
|
83
|
+
const capturedRequests = requestCapture.getRequests();
|
|
84
|
+
expect(capturedRequests).toMatchSnapshot("tunnel-create-requests");
|
|
85
|
+
const request = capturedRequests[0];
|
|
86
|
+
expect(request.method).toBe("POST");
|
|
87
|
+
expect(request.body).toEqual({
|
|
88
|
+
name: testTunnelName,
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
it("should handle tunnel creation error", async () => {
|
|
92
|
+
const testTunnelName = "invalid-tunnel";
|
|
93
|
+
const errorResponse = {
|
|
94
|
+
error: "Bad Request",
|
|
95
|
+
message: "Tunnel name already exists",
|
|
96
|
+
statusCode: 400,
|
|
97
|
+
};
|
|
98
|
+
const scope = setupAuthenticatedNock(nock(TEST_API_BASE))
|
|
99
|
+
.post(`/v1/accounts/${TEST_ACCOUNT_NAME}/tunnels`)
|
|
100
|
+
.reply(400, errorResponse);
|
|
101
|
+
scope.on("request", (req, interceptor, body) => {
|
|
102
|
+
requestCapture.capture(req, interceptor, body);
|
|
103
|
+
});
|
|
104
|
+
await executeTunnelCommand("create", {
|
|
105
|
+
account: TEST_ACCOUNT_NAME,
|
|
106
|
+
"api-key": TEST_AUTH_TOKEN,
|
|
107
|
+
"tunnel-name": testTunnelName,
|
|
108
|
+
});
|
|
109
|
+
expect(scope.isDone()).toBe(true);
|
|
110
|
+
expect(mockPrintDiagnostics).toHaveBeenCalled();
|
|
111
|
+
const capturedRequests = requestCapture.getRequests();
|
|
112
|
+
expect(capturedRequests).toMatchSnapshot("tunnel-create-error-requests");
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
describe("Tunnel List Command", () => {
|
|
116
|
+
it("should intercept GET request to list tunnels", async () => {
|
|
117
|
+
const mockResponse = {
|
|
118
|
+
data: [
|
|
119
|
+
{ id: "tunnel-123", name: "tunnel-1" },
|
|
120
|
+
{ id: "tunnel-456", name: "tunnel-2" },
|
|
121
|
+
],
|
|
122
|
+
};
|
|
123
|
+
const scope = setupAuthenticatedNock(nock(TEST_API_BASE))
|
|
124
|
+
.get(`/v1/accounts/${TEST_ACCOUNT_NAME}/tunnels`)
|
|
125
|
+
.reply(200, mockResponse);
|
|
126
|
+
scope.on("request", (req, interceptor, body) => {
|
|
127
|
+
requestCapture.capture(req, interceptor, body);
|
|
128
|
+
});
|
|
129
|
+
await executeTunnelCommand("list", {
|
|
130
|
+
account: TEST_ACCOUNT_NAME,
|
|
131
|
+
"api-key": TEST_AUTH_TOKEN,
|
|
132
|
+
});
|
|
133
|
+
expect(scope.isDone()).toBe(true);
|
|
134
|
+
const expectedTable = [
|
|
135
|
+
{ "tunnel-id": "tunnel-123", name: "tunnel-1" },
|
|
136
|
+
{ "tunnel-id": "tunnel-456", name: "tunnel-2" },
|
|
137
|
+
];
|
|
138
|
+
expect(mockPrintTable).toHaveBeenCalledWith(expectedTable);
|
|
139
|
+
const capturedRequests = requestCapture.getRequests();
|
|
140
|
+
expect(capturedRequests).toMatchSnapshot("tunnel-list-requests");
|
|
141
|
+
});
|
|
142
|
+
it("should handle empty tunnels list", async () => {
|
|
143
|
+
const mockResponse = { data: [] };
|
|
144
|
+
const scope = setupAuthenticatedNock(nock(TEST_API_BASE))
|
|
145
|
+
.get(`/v1/accounts/${TEST_ACCOUNT_NAME}/tunnels`)
|
|
146
|
+
.reply(200, mockResponse);
|
|
147
|
+
scope.on("request", (req, interceptor, body) => {
|
|
148
|
+
requestCapture.capture(req, interceptor, body);
|
|
149
|
+
});
|
|
150
|
+
await executeTunnelCommand("list", {
|
|
151
|
+
account: TEST_ACCOUNT_NAME,
|
|
152
|
+
"api-key": TEST_AUTH_TOKEN,
|
|
153
|
+
});
|
|
154
|
+
expect(scope.isDone()).toBe(true);
|
|
155
|
+
expect(mockPrintResult).toHaveBeenCalledWith("No tunnels found");
|
|
156
|
+
const capturedRequests = requestCapture.getRequests();
|
|
157
|
+
expect(capturedRequests).toMatchSnapshot("tunnel-list-empty-requests");
|
|
158
|
+
});
|
|
159
|
+
it("should handle API error responses", async () => {
|
|
160
|
+
const errorResponse = {
|
|
161
|
+
error: "Forbidden",
|
|
162
|
+
message: "Access denied",
|
|
163
|
+
statusCode: 403,
|
|
164
|
+
};
|
|
165
|
+
const scope = setupAuthenticatedNock(nock(TEST_API_BASE))
|
|
166
|
+
.get(`/v1/accounts/${TEST_ACCOUNT_NAME}/tunnels`)
|
|
167
|
+
.reply(403, errorResponse);
|
|
168
|
+
scope.on("request", (req, interceptor, body) => {
|
|
169
|
+
requestCapture.capture(req, interceptor, body);
|
|
170
|
+
});
|
|
171
|
+
await executeTunnelCommand("list", {
|
|
172
|
+
account: TEST_ACCOUNT_NAME,
|
|
173
|
+
"api-key": TEST_AUTH_TOKEN,
|
|
174
|
+
});
|
|
175
|
+
expect(scope.isDone()).toBe(true);
|
|
176
|
+
expect(mockPrintDiagnostics).toHaveBeenCalled();
|
|
177
|
+
const capturedRequests = requestCapture.getRequests();
|
|
178
|
+
expect(capturedRequests).toMatchSnapshot("tunnel-list-error-requests");
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
describe("Tunnel Delete Command", () => {
|
|
182
|
+
it("should intercept DELETE request to delete tunnel", async () => {
|
|
183
|
+
const tunnelId = "test-tunnel-123";
|
|
184
|
+
const mockTeardownResponse = {
|
|
185
|
+
id: "teardown-op-456",
|
|
186
|
+
status: "in-progress",
|
|
187
|
+
};
|
|
188
|
+
const mockCompletedTeardown = {
|
|
189
|
+
id: "teardown-op-456",
|
|
190
|
+
status: "success",
|
|
191
|
+
message: "Tunnel deleted successfully",
|
|
192
|
+
};
|
|
193
|
+
const deleteScope = setupAuthenticatedNock(nock(TEST_API_BASE))
|
|
194
|
+
.delete(`/v1/accounts/${TEST_ACCOUNT_NAME}/tunnels/${tunnelId}`)
|
|
195
|
+
.reply(200, mockTeardownResponse);
|
|
196
|
+
const pollScope = setupAuthenticatedNock(nock(TEST_API_BASE))
|
|
197
|
+
.get(`/v1/accounts/${TEST_ACCOUNT_NAME}/tunnels/${tunnelId}/teardown-operations/teardown-op-456`)
|
|
198
|
+
.reply(200, mockCompletedTeardown);
|
|
199
|
+
deleteScope.on("request", (req, interceptor, body) => {
|
|
200
|
+
requestCapture.capture(req, interceptor, body);
|
|
201
|
+
});
|
|
202
|
+
pollScope.on("request", (req, interceptor, body) => {
|
|
203
|
+
requestCapture.capture(req, interceptor, body);
|
|
204
|
+
});
|
|
205
|
+
await executeTunnelCommand("delete", {
|
|
206
|
+
account: TEST_ACCOUNT_NAME,
|
|
207
|
+
"api-key": TEST_AUTH_TOKEN,
|
|
208
|
+
"tunnel-id": tunnelId,
|
|
209
|
+
});
|
|
210
|
+
expect(deleteScope.isDone()).toBe(true);
|
|
211
|
+
expect(pollScope.isDone()).toBe(true);
|
|
212
|
+
expect(mockPrintResult).toHaveBeenCalledWith("Tunnel test-tunnel-123 deleted successfully.");
|
|
213
|
+
const capturedRequests = requestCapture.getRequests();
|
|
214
|
+
expect(capturedRequests).toMatchSnapshot("tunnel-delete-requests");
|
|
215
|
+
});
|
|
216
|
+
it("should handle tunnel deletion error", async () => {
|
|
217
|
+
const tunnelId = "nonexistent-tunnel";
|
|
218
|
+
const errorResponse = {
|
|
219
|
+
error: "Not Found",
|
|
220
|
+
message: "Tunnel not found",
|
|
221
|
+
statusCode: 404,
|
|
222
|
+
};
|
|
223
|
+
const scope = setupAuthenticatedNock(nock(TEST_API_BASE))
|
|
224
|
+
.delete(`/v1/accounts/${TEST_ACCOUNT_NAME}/tunnels/${tunnelId}`)
|
|
225
|
+
.reply(404, errorResponse);
|
|
226
|
+
scope.on("request", (req, interceptor, body) => {
|
|
227
|
+
requestCapture.capture(req, interceptor, body);
|
|
228
|
+
});
|
|
229
|
+
await executeTunnelCommand("delete", {
|
|
230
|
+
account: TEST_ACCOUNT_NAME,
|
|
231
|
+
"api-key": TEST_AUTH_TOKEN,
|
|
232
|
+
"tunnel-id": tunnelId,
|
|
233
|
+
});
|
|
234
|
+
expect(scope.isDone()).toBe(true);
|
|
235
|
+
expect(mockPrintDiagnostics).toHaveBeenCalled();
|
|
236
|
+
const capturedRequests = requestCapture.getRequests();
|
|
237
|
+
expect(capturedRequests).toMatchSnapshot("tunnel-delete-error-requests");
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
describe("Tunnel Describe Command", () => {
|
|
241
|
+
it("should intercept GET request to describe tunnel", async () => {
|
|
242
|
+
const tunnelId = "test-tunnel-123";
|
|
243
|
+
const mockTunnelDetails = {
|
|
244
|
+
id: tunnelId,
|
|
245
|
+
name: "Test Tunnel",
|
|
246
|
+
status: "active",
|
|
247
|
+
endpoint: "https://test-tunnel.zuplo.dev",
|
|
248
|
+
};
|
|
249
|
+
const scope = setupAuthenticatedNock(nock(TEST_API_BASE))
|
|
250
|
+
.get(`/v1/accounts/${TEST_ACCOUNT_NAME}/tunnels/${tunnelId}`)
|
|
251
|
+
.reply(200, mockTunnelDetails);
|
|
252
|
+
scope.on("request", (req, interceptor, body) => {
|
|
253
|
+
requestCapture.capture(req, interceptor, body);
|
|
254
|
+
});
|
|
255
|
+
await executeTunnelCommand("describe", {
|
|
256
|
+
account: TEST_ACCOUNT_NAME,
|
|
257
|
+
"api-key": TEST_AUTH_TOKEN,
|
|
258
|
+
"tunnel-id": tunnelId,
|
|
259
|
+
});
|
|
260
|
+
expect(scope.isDone()).toBe(true);
|
|
261
|
+
expect(mockPrintTable).toHaveBeenCalledWith(mockTunnelDetails);
|
|
262
|
+
const capturedRequests = requestCapture.getRequests();
|
|
263
|
+
expect(capturedRequests).toMatchSnapshot("tunnel-describe-requests");
|
|
264
|
+
});
|
|
265
|
+
it("should handle tunnel describe error", async () => {
|
|
266
|
+
const tunnelId = "nonexistent-tunnel";
|
|
267
|
+
const errorResponse = {
|
|
268
|
+
error: "Not Found",
|
|
269
|
+
message: "Tunnel not found",
|
|
270
|
+
statusCode: 404,
|
|
271
|
+
};
|
|
272
|
+
const scope = setupAuthenticatedNock(nock(TEST_API_BASE))
|
|
273
|
+
.get(`/v1/accounts/${TEST_ACCOUNT_NAME}/tunnels/${tunnelId}`)
|
|
274
|
+
.reply(404, errorResponse);
|
|
275
|
+
scope.on("request", (req, interceptor, body) => {
|
|
276
|
+
requestCapture.capture(req, interceptor, body);
|
|
277
|
+
});
|
|
278
|
+
await executeTunnelCommand("describe", {
|
|
279
|
+
account: TEST_ACCOUNT_NAME,
|
|
280
|
+
"api-key": TEST_AUTH_TOKEN,
|
|
281
|
+
"tunnel-id": tunnelId,
|
|
282
|
+
});
|
|
283
|
+
expect(scope.isDone()).toBe(true);
|
|
284
|
+
expect(mockPrintDiagnostics).toHaveBeenCalled();
|
|
285
|
+
const capturedRequests = requestCapture.getRequests();
|
|
286
|
+
expect(capturedRequests).toMatchSnapshot("tunnel-describe-error-requests");
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
describe("Tunnel Rotate Token Command", () => {
|
|
290
|
+
it("should intercept POST request to rotate tunnel token", async () => {
|
|
291
|
+
const tunnelId = "test-tunnel-123";
|
|
292
|
+
const mockTokenResponse = {
|
|
293
|
+
tunnelId: tunnelId,
|
|
294
|
+
newToken: "new-token-abc123",
|
|
295
|
+
message: "Token rotated successfully",
|
|
296
|
+
};
|
|
297
|
+
const scope = setupAuthenticatedNock(nock(TEST_API_BASE))
|
|
298
|
+
.post(`/v1/accounts/${TEST_ACCOUNT_NAME}/tunnels/${tunnelId}/$rotate-token`)
|
|
299
|
+
.reply(200, mockTokenResponse);
|
|
300
|
+
scope.on("request", (req, interceptor, body) => {
|
|
301
|
+
requestCapture.capture(req, interceptor, body);
|
|
302
|
+
});
|
|
303
|
+
await executeTunnelCommand("rotate-token", {
|
|
304
|
+
account: TEST_ACCOUNT_NAME,
|
|
305
|
+
"api-key": TEST_AUTH_TOKEN,
|
|
306
|
+
"tunnel-id": tunnelId,
|
|
307
|
+
});
|
|
308
|
+
expect(scope.isDone()).toBe(true);
|
|
309
|
+
expect(mockPrintTable).toHaveBeenCalledWith(mockTokenResponse);
|
|
310
|
+
const capturedRequests = requestCapture.getRequests();
|
|
311
|
+
expect(capturedRequests).toMatchSnapshot("tunnel-rotate-token-requests");
|
|
312
|
+
});
|
|
313
|
+
it("should handle token rotation error", async () => {
|
|
314
|
+
const tunnelId = "nonexistent-tunnel";
|
|
315
|
+
const errorResponse = {
|
|
316
|
+
error: "Forbidden",
|
|
317
|
+
message: "Access denied",
|
|
318
|
+
statusCode: 403,
|
|
319
|
+
};
|
|
320
|
+
const scope = setupAuthenticatedNock(nock(TEST_API_BASE))
|
|
321
|
+
.post(`/v1/accounts/${TEST_ACCOUNT_NAME}/tunnels/${tunnelId}/$rotate-token`)
|
|
322
|
+
.reply(403, errorResponse);
|
|
323
|
+
scope.on("request", (req, interceptor, body) => {
|
|
324
|
+
requestCapture.capture(req, interceptor, body);
|
|
325
|
+
});
|
|
326
|
+
await executeTunnelCommand("rotate-token", {
|
|
327
|
+
account: TEST_ACCOUNT_NAME,
|
|
328
|
+
"api-key": TEST_AUTH_TOKEN,
|
|
329
|
+
"tunnel-id": tunnelId,
|
|
330
|
+
});
|
|
331
|
+
expect(scope.isDone()).toBe(true);
|
|
332
|
+
expect(mockPrintDiagnostics).toHaveBeenCalled();
|
|
333
|
+
const capturedRequests = requestCapture.getRequests();
|
|
334
|
+
expect(capturedRequests).toMatchSnapshot("tunnel-rotate-token-error-requests");
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
describe("Tunnel Services Describe Command", () => {
|
|
338
|
+
it("should intercept GET request to describe tunnel services", async () => {
|
|
339
|
+
const tunnelId = "test-tunnel-123";
|
|
340
|
+
const mockServicesResponse = {
|
|
341
|
+
tunnelId: tunnelId,
|
|
342
|
+
services: [
|
|
343
|
+
{ name: "api-service", port: 3000, status: "running" },
|
|
344
|
+
{ name: "web-service", port: 8080, status: "running" },
|
|
345
|
+
],
|
|
346
|
+
};
|
|
347
|
+
const scope = setupAuthenticatedNock(nock(TEST_API_BASE))
|
|
348
|
+
.get(`/v1/accounts/${TEST_ACCOUNT_NAME}/tunnels/${tunnelId}/services-configuration`)
|
|
349
|
+
.reply(200, mockServicesResponse);
|
|
350
|
+
scope.on("request", (req, interceptor, body) => {
|
|
351
|
+
requestCapture.capture(req, interceptor, body);
|
|
352
|
+
});
|
|
353
|
+
await executeTunnelServicesCommand("describe", {
|
|
354
|
+
account: TEST_ACCOUNT_NAME,
|
|
355
|
+
"api-key": TEST_AUTH_TOKEN,
|
|
356
|
+
"tunnel-id": tunnelId,
|
|
357
|
+
});
|
|
358
|
+
expect(scope.isDone()).toBe(true);
|
|
359
|
+
expect(mockPrintResult).toHaveBeenCalledWith(JSON.stringify(mockServicesResponse, null, 2));
|
|
360
|
+
const capturedRequests = requestCapture.getRequests();
|
|
361
|
+
expect(capturedRequests).toMatchSnapshot("tunnel-services-describe-requests");
|
|
362
|
+
});
|
|
363
|
+
it("should handle services describe error", async () => {
|
|
364
|
+
const tunnelId = "nonexistent-tunnel";
|
|
365
|
+
const errorResponse = {
|
|
366
|
+
error: "Not Found",
|
|
367
|
+
message: "Tunnel not found",
|
|
368
|
+
statusCode: 404,
|
|
369
|
+
};
|
|
370
|
+
const scope = setupAuthenticatedNock(nock(TEST_API_BASE))
|
|
371
|
+
.get(`/v1/accounts/${TEST_ACCOUNT_NAME}/tunnels/${tunnelId}/services-configuration`)
|
|
372
|
+
.reply(404, errorResponse);
|
|
373
|
+
scope.on("request", (req, interceptor, body) => {
|
|
374
|
+
requestCapture.capture(req, interceptor, body);
|
|
375
|
+
});
|
|
376
|
+
await executeTunnelServicesCommand("describe", {
|
|
377
|
+
account: TEST_ACCOUNT_NAME,
|
|
378
|
+
"api-key": TEST_AUTH_TOKEN,
|
|
379
|
+
"tunnel-id": tunnelId,
|
|
380
|
+
});
|
|
381
|
+
expect(scope.isDone()).toBe(true);
|
|
382
|
+
expect(mockPrintDiagnostics).toHaveBeenCalled();
|
|
383
|
+
const capturedRequests = requestCapture.getRequests();
|
|
384
|
+
expect(capturedRequests).toMatchSnapshot("tunnel-services-describe-error-requests");
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
describe("Tunnel Services Update Command", () => {
|
|
388
|
+
it("should handle services update error due to file read failure", async () => {
|
|
389
|
+
const tunnelId = "test-tunnel-123";
|
|
390
|
+
const configFile = "/nonexistent/config.json";
|
|
391
|
+
await expect(executeTunnelServicesCommand("update", {
|
|
392
|
+
account: TEST_ACCOUNT_NAME,
|
|
393
|
+
"api-key": TEST_AUTH_TOKEN,
|
|
394
|
+
"tunnel-id": tunnelId,
|
|
395
|
+
"configuration-file": configFile,
|
|
396
|
+
})).rejects.toThrow("Process would exit");
|
|
397
|
+
const capturedRequests = requestCapture.getRequests();
|
|
398
|
+
expect(capturedRequests).toMatchSnapshot("tunnel-services-update-file-error-requests");
|
|
399
|
+
});
|
|
400
|
+
it("should handle services update API error", async () => {
|
|
401
|
+
const tunnelId = "test-tunnel-123";
|
|
402
|
+
const tempConfigFile = "/tmp/test-config.json";
|
|
403
|
+
const mockFs = require("node:fs/promises");
|
|
404
|
+
const originalReadFile = mockFs.readFile;
|
|
405
|
+
mockFs.readFile = jest.fn().mockResolvedValue('{"services": []}');
|
|
406
|
+
const errorResponse = {
|
|
407
|
+
error: "Bad Request",
|
|
408
|
+
message: "Invalid configuration file",
|
|
409
|
+
statusCode: 400,
|
|
410
|
+
};
|
|
411
|
+
const scope = setupAuthenticatedNock(nock(TEST_API_BASE))
|
|
412
|
+
.put(`/v1/accounts/${TEST_ACCOUNT_NAME}/tunnels/${tunnelId}/services-configuration`)
|
|
413
|
+
.reply(400, errorResponse);
|
|
414
|
+
scope.on("request", (req, interceptor, body) => {
|
|
415
|
+
requestCapture.capture(req, interceptor, body);
|
|
416
|
+
});
|
|
417
|
+
await executeTunnelServicesCommand("update", {
|
|
418
|
+
account: TEST_ACCOUNT_NAME,
|
|
419
|
+
"api-key": TEST_AUTH_TOKEN,
|
|
420
|
+
"tunnel-id": tunnelId,
|
|
421
|
+
"configuration-file": tempConfigFile,
|
|
422
|
+
});
|
|
423
|
+
expect(scope.isDone()).toBe(true);
|
|
424
|
+
expect(mockPrintDiagnostics).toHaveBeenCalled();
|
|
425
|
+
mockFs.readFile = originalReadFile;
|
|
426
|
+
const capturedRequests = requestCapture.getRequests();
|
|
427
|
+
expect(capturedRequests).toMatchSnapshot("tunnel-services-update-error-requests");
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
describe("Request validation", () => {
|
|
431
|
+
it("should include correct headers for create request", async () => {
|
|
432
|
+
const testTunnelName = "test-tunnel";
|
|
433
|
+
const mockResponse = {
|
|
434
|
+
id: "tunnel-123",
|
|
435
|
+
name: testTunnelName,
|
|
436
|
+
token: "tunnel-token-abc123",
|
|
437
|
+
};
|
|
438
|
+
const scope = nock(TEST_API_BASE)
|
|
439
|
+
.post(`/v1/accounts/${TEST_ACCOUNT_NAME}/tunnels`)
|
|
440
|
+
.matchHeader("authorization", `Bearer ${TEST_AUTH_TOKEN}`)
|
|
441
|
+
.matchHeader("content-type", "application/json")
|
|
442
|
+
.reply(201, mockResponse);
|
|
443
|
+
scope.on("request", (req, interceptor, body) => {
|
|
444
|
+
requestCapture.capture(req, interceptor, body);
|
|
445
|
+
});
|
|
446
|
+
await executeTunnelCommand("create", {
|
|
447
|
+
account: TEST_ACCOUNT_NAME,
|
|
448
|
+
"api-key": TEST_AUTH_TOKEN,
|
|
449
|
+
"tunnel-name": testTunnelName,
|
|
450
|
+
});
|
|
451
|
+
expect(scope.isDone()).toBe(true);
|
|
452
|
+
const capturedRequests = requestCapture.getRequests();
|
|
453
|
+
expect(capturedRequests).toMatchSnapshot("tunnel-create-headers-validation");
|
|
454
|
+
});
|
|
455
|
+
it("should include correct headers for list request", async () => {
|
|
456
|
+
const mockResponse = { data: [{ id: "tunnel-123", name: "tunnel-1" }] };
|
|
457
|
+
const scope = nock(TEST_API_BASE)
|
|
458
|
+
.get(`/v1/accounts/${TEST_ACCOUNT_NAME}/tunnels`)
|
|
459
|
+
.matchHeader("authorization", `Bearer ${TEST_AUTH_TOKEN}`)
|
|
460
|
+
.reply(200, mockResponse);
|
|
461
|
+
scope.on("request", (req, interceptor, body) => {
|
|
462
|
+
requestCapture.capture(req, interceptor, body);
|
|
463
|
+
});
|
|
464
|
+
await executeTunnelCommand("list", {
|
|
465
|
+
account: TEST_ACCOUNT_NAME,
|
|
466
|
+
"api-key": TEST_AUTH_TOKEN,
|
|
467
|
+
});
|
|
468
|
+
expect(scope.isDone()).toBe(true);
|
|
469
|
+
const capturedRequests = requestCapture.getRequests();
|
|
470
|
+
expect(capturedRequests).toMatchSnapshot("tunnel-list-headers-validation");
|
|
471
|
+
const request = capturedRequests[0];
|
|
472
|
+
expect(request.method).toBe("GET");
|
|
473
|
+
expect(request.body).toBeUndefined();
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
//# sourceMappingURL=tunnel.integration.test.js.map
|