| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 |
- /*
- Critical path (deep): file transfer, search and sharing.
- Extends the file-explorer coverage in 030 with the transfer-oriented
- File Manager features that move real bytes in and out of the system:
- multipart upload, media download (round-trip verification), search,
- and the share-link lifecycle (create / list / public download /
- delete). Uses the same endpoints the File Manager front end calls.
- */
- "use strict";
- const h = require("../lib/system-harness");
- const UPLOAD_BODY = "arozos-e2e upload payload " + Date.now();
- const UPLOAD_NAME = "e2e-upload.txt";
- h.run("FILE-TRANSFER", async function (env) {
- const base = env.baseURL;
- const page = await h.newPage(env.browser);
- await h.loginViaAPI(page, base, env.admin.username, env.admin.password);
- // Work inside a dedicated folder so the test is self-contained.
- let csrft = await h.csrfToken(page, base);
- let resp = await h.postForm(page, base + "/system/file_system/newItem", {
- type: "folder", src: "user:/", filename: "e2e-transfer", csrft: csrft
- });
- if (resp.toLowerCase().indexOf("ok") === -1) h.fail("could not create working folder: " + resp);
- // ── 1. Multipart upload of a real file ──
- const uploadResp = await page.request.post(base + "/system/file_system/upload", {
- multipart: {
- path: "user:/e2e-transfer",
- file: {
- name: UPLOAD_NAME,
- mimeType: "text/plain",
- buffer: Buffer.from(UPLOAD_BODY)
- }
- }
- });
- const uploadText = (await uploadResp.text()).trim().toLowerCase();
- if (uploadText.indexOf("ok") === -1) h.fail("upload failed: " + uploadText);
- let listing = await h.postForm(page, base + "/system/file_system/listDir", { dir: "user:/e2e-transfer" });
- if (listing.indexOf(UPLOAD_NAME) === -1) h.fail("uploaded file missing from listDir: " + listing.slice(0, 300));
- h.ok("multipart upload stores the file in the target folder");
- // ── 2. Download the file back and verify the bytes round-trip ──
- const dlResp = await page.request.get(base + "/media/?file=" +
- encodeURIComponent("user:/e2e-transfer/" + UPLOAD_NAME) + "&download=true");
- if (!dlResp.ok()) h.fail("download request failed with HTTP " + dlResp.status());
- const dlBody = await dlResp.text();
- if (dlBody !== UPLOAD_BODY) {
- h.fail("downloaded bytes did not match what was uploaded (got " + dlBody.length + " bytes)");
- }
- h.ok("download returns the exact bytes that were uploaded");
- // ── 3. Search finds the file by name ──
- const searchResults = await h.getJSON(page, base + "/system/file_system/search?path=" +
- encodeURIComponent("user:/e2e-transfer/") + "&keyword=e2e-upload");
- if (JSON.stringify(searchResults).indexOf(UPLOAD_NAME) === -1) {
- h.fail("search did not find the uploaded file: " + JSON.stringify(searchResults).slice(0, 300));
- }
- h.ok("file search finds the uploaded file by keyword");
- // ── 4. Create a share link for the file ──
- const share = await h.getJSON(page, base + "/system/file_system/share/new?path=" +
- encodeURIComponent("user:/e2e-transfer/" + UPLOAD_NAME));
- // share/new accepts POST too, but the front end also uses GET-style; use POST for parity.
- let shareObj = share;
- if (!shareObj || !shareObj.UUID) {
- // Fall back to the POST form the File Manager actually submits.
- const shareText = await h.postForm(page, base + "/system/file_system/share/new", {
- path: "user:/e2e-transfer/" + UPLOAD_NAME
- });
- try { shareObj = JSON.parse(shareText); } catch (e) { shareObj = null; }
- }
- if (!shareObj || !shareObj.UUID) h.fail("share/new did not return a share UUID: " + JSON.stringify(share));
- const shareUUID = shareObj.UUID;
- h.ok("share/new creates a share link with a UUID");
- // ── 5. The share appears in the user's share list ──
- const shareList = await h.getJSON(page, base + "/system/file_system/share/list");
- if (JSON.stringify(shareList).indexOf(shareUUID) === -1) {
- h.fail("created share not present in share list");
- }
- h.ok("the share appears in the user's share list");
- // ── 6. The public share link serves the file without a session ──
- // /share/download/{uuid}/ is the direct-download endpoint (the plain
- // /share/{uuid} path renders the preview page instead).
- const anon = await h.newPage(env.browser);
- const anonDl = await anon.request.get(base + "/share/download/" + shareUUID + "/");
- if (!anonDl.ok()) h.fail("public share download failed with HTTP " + anonDl.status());
- const anonBody = await anonDl.text();
- if (anonBody !== UPLOAD_BODY) {
- h.fail("public share download bytes did not match the original file");
- }
- // The plain share page should also be reachable anonymously (preview).
- const anonPage = await anon.request.get(base + "/share/" + shareUUID + "/");
- if (!anonPage.ok()) h.fail("public share preview page failed with HTTP " + anonPage.status());
- h.ok("the public share link serves the file and preview page without a session");
- await anon.close();
- // ── 7. Deleting the share revokes public access ──
- resp = await h.postForm(page, base + "/system/file_system/share/delete", { uuid: shareUUID });
- if (resp.toLowerCase().indexOf("ok") === -1) h.fail("share delete failed: " + resp);
- const shareListAfter = await h.getJSON(page, base + "/system/file_system/share/list");
- if (JSON.stringify(shareListAfter).indexOf(shareUUID) !== -1) {
- h.fail("deleted share still present in share list");
- }
- h.ok("deleting the share removes it from the share list");
- // ── Cleanup ──
- csrft = await h.csrfToken(page, base);
- await h.postForm(page, base + "/system/file_system/fileOpr", {
- opr: "delete", src: JSON.stringify(["user:/e2e-transfer"]), csrft: csrft
- });
- await page.close();
- });
|