021-desktop-api.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. /*
  2. Desktop: backend API surface.
  3. Exercises every endpoint registered by DesktopInit in src/desktop.go,
  4. driving the exact request shapes the desktop.html front end uses:
  5. /system/desktop/host host / device details
  6. /system/desktop/user self info (quota, groups, admin),
  7. another user's public info, noicon
  8. /system/desktop/theme list wallpapers, set + read back
  9. /system/desktop/preference set / get / remove a preference key
  10. /system/desktop/files icon location set / get / delete
  11. /system/desktop/createShortcut create a .shortcut on the desktop
  12. /system/desktop/listDesktop list desktop objects + shortcut meta
  13. /system/desktop/opr/renameShortcut rename it, and reject off-desktop
  14. All backend, so this is the deterministic backbone of the desktop
  15. coverage; the UI specs (022, 023) drive the same features visually.
  16. */
  17. "use strict";
  18. const h = require("../lib/system-harness");
  19. const OTHER_USER = "e2edeskother";
  20. const OTHER_PASS = "e2e-Other-Pass1";
  21. const SC_NAME = "e2e-shortcut";
  22. const SC_FILE = "user:/Desktop/" + SC_NAME + ".shortcut";
  23. h.run("DESKTOP-API", async function (env) {
  24. const base = env.baseURL;
  25. const page = await h.newPage(env.browser);
  26. await h.loginViaAPI(page, base, env.admin.username, env.admin.password);
  27. // ── 1. /host reports device details ──
  28. const host = await h.getJSON(page, base + "/system/desktop/host");
  29. ["Hostname", "DeviceUUID", "BuildVersion", "InternalVersion"].forEach(function (k) {
  30. if (!(k in host)) h.fail("/system/desktop/host missing field " + k);
  31. });
  32. if (host.Hostname.indexOf("E2E ArozOS") === -1) {
  33. h.fail("/system/desktop/host wrong hostname: " + host.Hostname);
  34. }
  35. h.ok("/host returns hostname, device UUID and version fields");
  36. // ── 2. /user reports the signed-in user's profile ──
  37. const me = await h.getJSON(page, base + "/system/desktop/user");
  38. if (me.Username !== env.admin.username) h.fail("/user wrong username: " + me.Username);
  39. if (me.IsAdmin !== true) h.fail("/user should mark the admin account as admin");
  40. if (!Array.isArray(me.UserGroups) || me.UserGroups.indexOf("administrator") === -1) {
  41. h.fail("/user should list the administrator group: " + JSON.stringify(me.UserGroups));
  42. }
  43. if (typeof me.StorageQuotaTotal !== "number") h.fail("/user missing StorageQuotaTotal");
  44. h.ok("/user reports username, admin flag, groups and storage quota");
  45. // noicon=true (POST param) strips the (potentially large) icon payload.
  46. const meNoIcon = JSON.parse(await h.postForm(page, base + "/system/desktop/user", { noicon: "true" }));
  47. if (meNoIcon.UserIcon !== "") h.fail("/user noicon=true should return an empty UserIcon");
  48. h.ok("/user with noicon=true omits the icon payload");
  49. // Another user's *public* info via ?target= (create one, query, remove).
  50. let resp = await h.postForm(page, base + "/system/auth/register", {
  51. username: OTHER_USER, password: OTHER_PASS, group: "user"
  52. });
  53. if (resp.toLowerCase().indexOf("ok") === -1) h.fail("could not create second user: " + resp);
  54. const other = await h.getJSON(page, base + "/system/desktop/user?target=" + encodeURIComponent(OTHER_USER));
  55. if (other.Username !== OTHER_USER) h.fail("/user?target did not return the target user");
  56. if (other.IsAdmin !== false) h.fail("/user?target should not mark a plain user as admin");
  57. h.ok("/user?target returns another user's public info");
  58. const missing = await page.request.get(base + "/system/desktop/user?target=nosuchuser____");
  59. if ((await missing.text()).toLowerCase().indexOf("error") === -1) {
  60. h.fail("/user?target for a missing user should error");
  61. }
  62. h.ok("/user?target for a non-existent user returns an error");
  63. // ── 3. /theme lists wallpapers and round-trips the user's choice ──
  64. const themes = await h.getJSON(page, base + "/system/desktop/theme");
  65. if (!Array.isArray(themes) || themes.length === 0) h.fail("/theme returned no wallpaper themes");
  66. if (!("Theme" in themes[0]) || !("Bglist" in themes[0])) {
  67. h.fail("/theme entries missing Theme/Bglist fields: " + JSON.stringify(themes[0]));
  68. }
  69. h.ok("/theme lists wallpaper themes with their backgrounds");
  70. const chosen = themes[0].Theme;
  71. const setThemeResp = (await (await page.request.get(base + "/system/desktop/theme?set=" +
  72. encodeURIComponent(chosen))).text()).trim();
  73. if (setThemeResp.replace(/"/g, "").toLowerCase() !== "ok") h.fail("/theme?set failed: " + setThemeResp);
  74. const gotTheme = (await (await page.request.get(base + "/system/desktop/theme?get=true")).text()).trim();
  75. if (gotTheme.replace(/"/g, "") !== chosen) {
  76. h.fail("/theme?get did not return the theme just set (" + gotTheme + " != " + chosen + ")");
  77. }
  78. h.ok("/theme?set then ?get round-trips the selected wallpaper theme");
  79. // ── 4. /preference set / get / remove ──
  80. const prefKey = "e2e_pref";
  81. const prefVal = "value-" + Date.now();
  82. resp = await h.postForm(page, base + "/system/desktop/preference", { preference: prefKey, value: prefVal });
  83. if (resp.toLowerCase().indexOf("ok") === -1) h.fail("preference set failed: " + resp);
  84. let gotPref = JSON.parse(await h.postForm(page, base + "/system/desktop/preference", { preference: prefKey }));
  85. if (gotPref !== prefVal) h.fail("preference get did not return the stored value: " + gotPref);
  86. h.ok("/preference stores and returns a preference value");
  87. resp = await h.postForm(page, base + "/system/desktop/preference", { preference: prefKey, remove: "true" });
  88. if (resp.toLowerCase().indexOf("ok") === -1) h.fail("preference remove failed: " + resp);
  89. gotPref = JSON.parse(await h.postForm(page, base + "/system/desktop/preference", { preference: prefKey }));
  90. if (gotPref !== "") h.fail("preference should be empty after removal, got: " + gotPref);
  91. h.ok("/preference removes a preference key");
  92. // ── 5. createShortcut puts a .shortcut on the desktop ──
  93. resp = await h.postForm(page, base + "/system/desktop/createShortcut", {
  94. stype: "module", stext: SC_NAME, spath: "Dummy/index.html", sicon: "img/system/favicon.png"
  95. });
  96. if (resp.toLowerCase().indexOf("ok") === -1) h.fail("createShortcut failed: " + resp);
  97. h.ok("/createShortcut writes a shortcut file to the desktop");
  98. // ── 6. listDesktop reports the shortcut with parsed metadata ──
  99. let desktop = await h.getJSON(page, base + "/system/desktop/listDesktop");
  100. let sc = desktop.filter(function (o) { return o.Filename === SC_NAME + ".shortcut"; })[0];
  101. if (!sc) h.fail("listDesktop did not include the created shortcut");
  102. if (!sc.IsShortcut || sc.ShortcutType !== "module" || sc.ShortcutName !== SC_NAME) {
  103. h.fail("listDesktop shortcut metadata wrong: " + JSON.stringify(sc));
  104. }
  105. h.ok("/listDesktop returns the shortcut with IsShortcut + parsed name/type");
  106. // ── 7. icon location set / get / delete via /files ──
  107. resp = await h.postForm(page, base + "/system/desktop/files", {
  108. set: SC_NAME + ".shortcut", x: "123", y: "456"
  109. });
  110. if (resp.replace(/"/g, "").toLowerCase() !== "ok") h.fail("/files set location failed: " + resp);
  111. const loc = JSON.parse(await h.postForm(page, base + "/system/desktop/files", { get: SC_NAME + ".shortcut" }));
  112. if (loc[0] !== 123 || loc[1] !== 456) h.fail("/files get returned wrong location: " + JSON.stringify(loc));
  113. h.ok("/files stores and returns a desktop icon coordinate");
  114. await h.postForm(page, base + "/system/desktop/files", { del: SC_NAME + ".shortcut" });
  115. const locGone = JSON.parse(await h.postForm(page, base + "/system/desktop/files", { get: SC_NAME + ".shortcut" }));
  116. if (locGone[0] !== -1 || locGone[1] !== -1) h.fail("/files del did not clear the location: " + JSON.stringify(locGone));
  117. h.ok("/files deletes a desktop icon coordinate");
  118. // ── 8. renameShortcut updates the shortcut's display name ──
  119. const newName = "e2e-renamed";
  120. resp = (await (await page.request.get(base + "/system/desktop/opr/renameShortcut?src=" +
  121. encodeURIComponent(SC_FILE) + "&new=" + encodeURIComponent(newName))).text()).trim();
  122. if (resp.toLowerCase().indexOf("ok") === -1) h.fail("renameShortcut failed: " + resp);
  123. desktop = await h.getJSON(page, base + "/system/desktop/listDesktop");
  124. sc = desktop.filter(function (o) { return o.Filename === SC_NAME + ".shortcut"; })[0];
  125. if (!sc || sc.ShortcutName !== newName) {
  126. h.fail("renameShortcut did not update the display name: " + JSON.stringify(sc));
  127. }
  128. h.ok("/opr/renameShortcut updates the shortcut display name");
  129. // Off-desktop rename must be refused.
  130. const badRename = (await (await page.request.get(base + "/system/desktop/opr/renameShortcut?src=" +
  131. encodeURIComponent("user:/Documents/whatever.shortcut") + "&new=x")).text()).trim();
  132. if (badRename.toLowerCase().indexOf("error") === -1) {
  133. h.fail("renameShortcut on a non-desktop path should be refused: " + badRename);
  134. }
  135. h.ok("/opr/renameShortcut refuses a path outside the desktop");
  136. // ── Cleanup ──
  137. const csrft = await h.csrfToken(page, base);
  138. await h.postForm(page, base + "/system/file_system/fileOpr", {
  139. opr: "delete", src: JSON.stringify([SC_FILE]), csrft: csrft
  140. });
  141. await h.postForm(page, base + "/system/users/removeUser", { username: OTHER_USER });
  142. await page.close();
  143. });