uplink-cli 0.1.0-alpha.1 → 0.1.0-alpha.2
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/cli/src/subcommands/menu.ts +187 -1
- package/package.json +1 -1
|
@@ -1204,12 +1204,26 @@ function findTunnelClients(): Array<{ pid: number; port: number; token: string }
|
|
|
1204
1204
|
|
|
1205
1205
|
function runSmoke(script: "smoke:tunnel" | "smoke:db" | "smoke:all" | "test:comprehensive") {
|
|
1206
1206
|
return new Promise<void>((resolve, reject) => {
|
|
1207
|
+
const projectRoot = join(__dirname, "../../..");
|
|
1207
1208
|
const env = {
|
|
1208
1209
|
...process.env,
|
|
1209
1210
|
AGENTCLOUD_API_BASE: process.env.AGENTCLOUD_API_BASE ?? "https://api.uplink.spot",
|
|
1210
1211
|
AGENTCLOUD_TOKEN: process.env.AGENTCLOUD_TOKEN ?? "dev-token",
|
|
1211
1212
|
};
|
|
1212
|
-
|
|
1213
|
+
|
|
1214
|
+
// For test:comprehensive, run inline (no subprocess)
|
|
1215
|
+
if (script === "test:comprehensive") {
|
|
1216
|
+
runComprehensiveTest(env).then(resolve).catch(reject);
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// For other scripts, use npm run with shell mode from project root
|
|
1221
|
+
const child = spawn("npm", ["run", script], {
|
|
1222
|
+
stdio: "inherit",
|
|
1223
|
+
env,
|
|
1224
|
+
cwd: projectRoot,
|
|
1225
|
+
shell: true
|
|
1226
|
+
});
|
|
1213
1227
|
child.on("close", (code) => {
|
|
1214
1228
|
if (code === 0) {
|
|
1215
1229
|
resolve();
|
|
@@ -1220,3 +1234,175 @@ function runSmoke(script: "smoke:tunnel" | "smoke:db" | "smoke:all" | "test:comp
|
|
|
1220
1234
|
child.on("error", (err) => reject(err));
|
|
1221
1235
|
});
|
|
1222
1236
|
}
|
|
1237
|
+
|
|
1238
|
+
// Inline comprehensive test (no subprocess needed)
|
|
1239
|
+
async function runComprehensiveTest(env: Record<string, string | undefined>) {
|
|
1240
|
+
const API_BASE = env.AGENTCLOUD_API_BASE || "https://api.uplink.spot";
|
|
1241
|
+
const ADMIN_TOKEN = env.AGENTCLOUD_TOKEN || "";
|
|
1242
|
+
|
|
1243
|
+
const c = {
|
|
1244
|
+
reset: "\x1b[0m",
|
|
1245
|
+
red: "\x1b[31m",
|
|
1246
|
+
green: "\x1b[32m",
|
|
1247
|
+
yellow: "\x1b[33m",
|
|
1248
|
+
blue: "\x1b[34m",
|
|
1249
|
+
};
|
|
1250
|
+
|
|
1251
|
+
let PASSED = 0;
|
|
1252
|
+
let FAILED = 0;
|
|
1253
|
+
let SKIPPED = 0;
|
|
1254
|
+
|
|
1255
|
+
const logPass = (msg: string) => { console.log(`${c.green}✅ PASS${c.reset}: ${msg}`); PASSED++; };
|
|
1256
|
+
const logFail = (msg: string) => { console.log(`${c.red}❌ FAIL${c.reset}: ${msg}`); FAILED++; };
|
|
1257
|
+
const logSkip = (msg: string) => { console.log(`${c.yellow}⏭️ SKIP${c.reset}: ${msg}`); SKIPPED++; };
|
|
1258
|
+
const logInfo = (msg: string) => { console.log(`${c.blue}ℹ️ INFO${c.reset}: ${msg}`); };
|
|
1259
|
+
const logSection = (title: string) => {
|
|
1260
|
+
console.log(`\n${c.blue}═══════════════════════════════════════════════════════════${c.reset}`);
|
|
1261
|
+
console.log(`${c.blue} ${title}${c.reset}`);
|
|
1262
|
+
console.log(`${c.blue}═══════════════════════════════════════════════════════════${c.reset}`);
|
|
1263
|
+
};
|
|
1264
|
+
|
|
1265
|
+
const api = async (method: string, path: string, body?: object, token?: string) => {
|
|
1266
|
+
try {
|
|
1267
|
+
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
1268
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
1269
|
+
|
|
1270
|
+
const res = await fetch(`${API_BASE}${path}`, {
|
|
1271
|
+
method,
|
|
1272
|
+
headers,
|
|
1273
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
1274
|
+
});
|
|
1275
|
+
|
|
1276
|
+
let responseBody: any;
|
|
1277
|
+
try { responseBody = await res.json(); } catch { responseBody = {}; }
|
|
1278
|
+
return { status: res.status, body: responseBody };
|
|
1279
|
+
} catch (err: any) {
|
|
1280
|
+
return { status: 0, body: { error: err.message } };
|
|
1281
|
+
}
|
|
1282
|
+
};
|
|
1283
|
+
|
|
1284
|
+
console.log("");
|
|
1285
|
+
console.log("╔═══════════════════════════════════════════════════════════╗");
|
|
1286
|
+
console.log("║ UPLINK COMPREHENSIVE TEST SUITE ║");
|
|
1287
|
+
console.log("╚═══════════════════════════════════════════════════════════╝");
|
|
1288
|
+
console.log(`\nAPI Base: ${API_BASE}\n`);
|
|
1289
|
+
|
|
1290
|
+
if (!ADMIN_TOKEN) {
|
|
1291
|
+
console.log(`${c.red}ERROR: AGENTCLOUD_TOKEN not set.${c.reset}`);
|
|
1292
|
+
throw new Error("AGENTCLOUD_TOKEN not set");
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// 1. Health
|
|
1296
|
+
logSection("1. HEALTH CHECKS");
|
|
1297
|
+
let res = await api("GET", "/health");
|
|
1298
|
+
if (res.status === 200 && res.body?.status === "ok") logPass("GET /health returns 200");
|
|
1299
|
+
else logFail(`GET /health - got ${res.status}`);
|
|
1300
|
+
|
|
1301
|
+
res = await api("GET", "/health/live");
|
|
1302
|
+
if (res.status === 200) logPass("GET /health/live returns 200");
|
|
1303
|
+
else logFail(`GET /health/live - got ${res.status}`);
|
|
1304
|
+
|
|
1305
|
+
// 2. Auth
|
|
1306
|
+
logSection("2. AUTHENTICATION");
|
|
1307
|
+
res = await api("GET", "/v1/me");
|
|
1308
|
+
if (res.status === 401) logPass("Missing token returns 401");
|
|
1309
|
+
else logFail(`Missing token - got ${res.status}`);
|
|
1310
|
+
|
|
1311
|
+
res = await api("GET", "/v1/me", undefined, "invalid-token");
|
|
1312
|
+
if (res.status === 401) logPass("Invalid token returns 401");
|
|
1313
|
+
else logFail(`Invalid token - got ${res.status}`);
|
|
1314
|
+
|
|
1315
|
+
res = await api("GET", "/v1/me", undefined, ADMIN_TOKEN);
|
|
1316
|
+
if (res.status === 200 && res.body?.role === "admin") logPass("Valid admin token works");
|
|
1317
|
+
else logFail(`Admin token - got ${res.status}`);
|
|
1318
|
+
|
|
1319
|
+
// 3. Signup
|
|
1320
|
+
logSection("3. SIGNUP FLOW");
|
|
1321
|
+
let USER_TOKEN = "";
|
|
1322
|
+
let USER_TOKEN_ID = "";
|
|
1323
|
+
res = await api("POST", "/v1/signup", { label: `test-${Date.now()}` });
|
|
1324
|
+
if (res.status === 201 && res.body?.token) {
|
|
1325
|
+
USER_TOKEN = res.body.token;
|
|
1326
|
+
USER_TOKEN_ID = res.body.id;
|
|
1327
|
+
logPass("POST /v1/signup creates token");
|
|
1328
|
+
if (res.body.role === "user") logPass("Signup creates user role");
|
|
1329
|
+
else logFail(`Signup role: ${res.body.role}`);
|
|
1330
|
+
} else if (res.status === 429) {
|
|
1331
|
+
logSkip("Signup rate limited");
|
|
1332
|
+
} else {
|
|
1333
|
+
logFail(`Signup - got ${res.status}`);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// 4. Authorization
|
|
1337
|
+
logSection("4. AUTHORIZATION");
|
|
1338
|
+
if (USER_TOKEN) {
|
|
1339
|
+
res = await api("GET", "/v1/admin/stats", undefined, USER_TOKEN);
|
|
1340
|
+
if (res.status === 403) logPass("User blocked from admin endpoint");
|
|
1341
|
+
else logFail(`User accessed admin - got ${res.status}`);
|
|
1342
|
+
} else {
|
|
1343
|
+
logSkip("No user token for auth tests");
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
// 5. Tunnels
|
|
1347
|
+
logSection("5. TUNNEL API");
|
|
1348
|
+
res = await api("GET", "/v1/tunnels", undefined, ADMIN_TOKEN);
|
|
1349
|
+
if (res.status === 200) logPass("GET /v1/tunnels works");
|
|
1350
|
+
else logFail(`Tunnels list - got ${res.status}`);
|
|
1351
|
+
|
|
1352
|
+
res = await api("POST", "/v1/tunnels", { port: 3000 }, ADMIN_TOKEN);
|
|
1353
|
+
if (res.status === 201) {
|
|
1354
|
+
logPass("POST /v1/tunnels creates tunnel");
|
|
1355
|
+
if (res.body?.id) {
|
|
1356
|
+
const delRes = await api("DELETE", `/v1/tunnels/${res.body.id}`, undefined, ADMIN_TOKEN);
|
|
1357
|
+
if (delRes.status === 200) logPass("DELETE tunnel works");
|
|
1358
|
+
else logFail(`Delete tunnel - got ${delRes.status}`);
|
|
1359
|
+
}
|
|
1360
|
+
} else {
|
|
1361
|
+
logFail(`Create tunnel - got ${res.status}`);
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// 6. Databases
|
|
1365
|
+
logSection("6. DATABASE API");
|
|
1366
|
+
res = await api("GET", "/v1/dbs", undefined, ADMIN_TOKEN);
|
|
1367
|
+
if (res.status === 200) logPass("GET /v1/dbs works");
|
|
1368
|
+
else logFail(`Databases list - got ${res.status}`);
|
|
1369
|
+
logInfo("Skipping DB creation (provisions real resources)");
|
|
1370
|
+
|
|
1371
|
+
// 7. Admin Stats
|
|
1372
|
+
logSection("7. ADMIN STATS");
|
|
1373
|
+
res = await api("GET", "/v1/admin/stats", undefined, ADMIN_TOKEN);
|
|
1374
|
+
if (res.status === 200) {
|
|
1375
|
+
logPass("GET /v1/admin/stats works");
|
|
1376
|
+
if (res.body?.tunnels !== undefined) logPass("Stats include tunnels");
|
|
1377
|
+
if (res.body?.databases !== undefined) logPass("Stats include databases");
|
|
1378
|
+
} else {
|
|
1379
|
+
logFail(`Admin stats - got ${res.status}`);
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// Cleanup
|
|
1383
|
+
logSection("8. CLEANUP");
|
|
1384
|
+
if (USER_TOKEN_ID) {
|
|
1385
|
+
res = await api("DELETE", `/v1/admin/tokens/${USER_TOKEN_ID}`, undefined, ADMIN_TOKEN);
|
|
1386
|
+
if (res.status === 200) logPass("Cleaned up test token");
|
|
1387
|
+
else logInfo("Could not clean up token");
|
|
1388
|
+
} else {
|
|
1389
|
+
logInfo("No test token to clean up");
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// Summary
|
|
1393
|
+
logSection("TEST SUMMARY");
|
|
1394
|
+
console.log(`\n ${c.green}Passed${c.reset}: ${PASSED}`);
|
|
1395
|
+
console.log(` ${c.red}Failed${c.reset}: ${FAILED}`);
|
|
1396
|
+
console.log(` ${c.yellow}Skipped${c.reset}: ${SKIPPED}\n`);
|
|
1397
|
+
|
|
1398
|
+
if (FAILED === 0) {
|
|
1399
|
+
console.log(`${c.green}═══════════════════════════════════════════════════════════${c.reset}`);
|
|
1400
|
+
console.log(`${c.green} ✅ ALL TESTS PASSED (${PASSED}/${PASSED + FAILED})${c.reset}`);
|
|
1401
|
+
console.log(`${c.green}═══════════════════════════════════════════════════════════${c.reset}`);
|
|
1402
|
+
} else {
|
|
1403
|
+
console.log(`${c.red}═══════════════════════════════════════════════════════════${c.reset}`);
|
|
1404
|
+
console.log(`${c.red} ❌ SOME TESTS FAILED (${FAILED}/${PASSED + FAILED})${c.reset}`);
|
|
1405
|
+
console.log(`${c.red}═══════════════════════════════════════════════════════════${c.reset}`);
|
|
1406
|
+
throw new Error(`${FAILED} tests failed`);
|
|
1407
|
+
}
|
|
1408
|
+
}
|