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.
@@ -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
- const child = spawn("npm", ["run", script], { stdio: "inherit", env });
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uplink-cli",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.2",
4
4
  "description": "Localhost to public URL in seconds. No signup forms, no browser - everything in your terminal.",
5
5
  "keywords": [
6
6
  "tunnel",