Files
quak/test/integration/live-login.ts
sneak d8a4b0291e Rename quack to quak
German for 'quack', matching the Ente (German for 'duck') naming. All
references updated: package name, CLI binary, X-Client-Package header,
test descriptions, temp dir prefixes, README, Makefile docker tag.
2026-05-13 18:02:55 -07:00

116 lines
4.1 KiB
TypeScript

import { init } from "../../src/crypto/index.js";
import { ApiClient } from "../../src/api/client.js";
import { beginLogin } from "../../src/auth/login.js";
import { unwrapAuth } from "../../src/auth/unwrap.js";
import { decryptCollection, decryptFile } from "../../src/model/index.js";
import { downloadFile } from "../../src/download/index.js";
import type { RawCollection, RawEnteFile } from "../../src/model/index.js";
// Dev account — not a secret, throwaway account for integration testing.
const EMAIL = "entedev2026jp@acidhou.se";
const PASSWORD = "loldongs";
const RECOVERY_KEY =
"deliver have behave collect void chicken boring embrace coast reflect squeeze cotton dish resemble license remain quick dwarf plastic ensure amused cry nasty equip";
void RECOVERY_KEY;
const main = async () => {
await init();
const api = new ApiClient();
console.log(`Logging in as ${EMAIL}...`);
const challenge = await beginLogin(api, EMAIL, PASSWORD);
if (challenge.kind !== "complete") {
console.error("Expected complete login, got:", challenge.kind);
process.exit(1);
}
const { masterKey, token } = await unwrapAuth(challenge.response, PASSWORD);
api.setAuthToken(token);
console.log("Logged in, user ID:", challenge.response.id);
// Fetch and decrypt collections
console.log("\nFetching collections...");
const { collections: rawCollections } = await api.getJSON<{
collections: RawCollection[];
}>("/collections/v2", { sinceTime: 0 });
const userID = challenge.response.id;
const collections = rawCollections.map((raw) =>
decryptCollection(raw, masterKey, userID),
);
console.log(`${collections.length} collection(s):`);
for (const c of collections) {
console.log(
` [${c.type}] "${c.name}" (id=${c.id}, shared=${c.isShared})`,
);
}
// Fetch and decrypt files from the first non-empty collection
for (const col of collections) {
console.log(`\nFetching files from "${col.name}" (id=${col.id})...`);
const { diff: rawFiles } = await api.getJSON<{
diff: RawEnteFile[];
hasMore: boolean;
}>("/collections/v2/diff", {
collectionID: col.id,
sinceTime: 0,
});
if (rawFiles.length === 0) {
console.log(" (empty)");
continue;
}
const files = rawFiles
.filter((f) => !f.isDeleted)
.map((raw) => decryptFile(raw, col.key));
console.log(` ${files.length} file(s):`);
for (const f of files.slice(0, 10)) {
console.log(
` ${f.metadata.title} [${f.metadata.fileType}] id=${f.id}`,
);
if (f.metadata.latitude !== undefined) {
console.log(
` location: ${f.metadata.latitude}, ${f.metadata.longitude}`,
);
}
}
if (files.length > 10) {
console.log(` ... and ${files.length - 10} more`);
}
// Download the first file to a temp directory
if (files.length > 0) {
const first = files[0]!;
const { mkdtempSync, statSync } = await import("node:fs");
const { join } = await import("node:path");
const { tmpdir } = await import("node:os");
const outDir = mkdtempSync(join(tmpdir(), "quak-live-test-"));
const outPath = `${outDir}/${first.metadata.title}`;
console.log(`\n Downloading "${first.metadata.title}"...`);
const result = await downloadFile(api, first, outPath);
const stat = statSync(result.path);
console.log(
` Saved to ${result.path} (${result.bytesWritten} bytes decrypted, ${stat.size} on disk)`,
);
if (result.bytesWritten < 1000) {
console.error(
" WARNING: file seems too small, possible decryption issue",
);
}
}
break;
}
console.log("\nLive integration test passed.");
};
main().catch((err) => {
console.error("FAILED:", err);
process.exit(1);
});