feat: refine account management and targeted editing
Add local provider icons, larger compact account rows, the requested column order, and full username display while keeping secrets masked. Replace provider-wide editing with a row-specific editor that preserves sibling accounts, exact limits, live usage counters, and status metadata. Add targeted credential validation, release notices, and regression coverage.
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { defaultSettings } from "../src/main/constants";
|
||||
import {
|
||||
applyAccountEdit,
|
||||
buildAccountEditCheckSettings,
|
||||
createAccountEditState,
|
||||
removeAccountTarget,
|
||||
validateAccountEdit,
|
||||
validateAccountEditStatuses,
|
||||
type AccountEditTarget
|
||||
} from "../src/renderer/account-edit";
|
||||
import { getDebridLinkApiKeyId } from "../src/shared/debrid-link-keys";
|
||||
import { getMegaDebridAccountId } from "../src/shared/mega-debrid-accounts";
|
||||
|
||||
const GIB = 1024 * 1024 * 1024;
|
||||
|
||||
function megaTarget(login: string): AccountEditTarget {
|
||||
return {
|
||||
type: "mega",
|
||||
rowKey: `mega-${getMegaDebridAccountId(login)}`,
|
||||
kind: "megadebrid-api",
|
||||
service: "megadebrid-api",
|
||||
accountId: getMegaDebridAccountId(login)
|
||||
};
|
||||
}
|
||||
|
||||
function debridLinkTarget(token: string): AccountEditTarget {
|
||||
return {
|
||||
type: "debridlink",
|
||||
rowKey: `dl-${getDebridLinkApiKeyId(token)}`,
|
||||
kind: "debridlink-api",
|
||||
service: "debridlink",
|
||||
keyId: getDebridLinkApiKeyId(token)
|
||||
};
|
||||
}
|
||||
|
||||
describe("account-specific editing", () => {
|
||||
it("changes only the selected Mega-Debrid account and preserves sibling order and mode settings", () => {
|
||||
const oldId = getMegaDebridAccountId("second@example.com");
|
||||
const newId = getMegaDebridAccountId("renamed@example.com");
|
||||
const firstId = getMegaDebridAccountId("first@example.com");
|
||||
const settings = {
|
||||
...defaultSettings(),
|
||||
megaCredentials: "first@example.com:first-pass\nsecond@example.com:second-pass\nthird@example.com:third-pass",
|
||||
megaLogin: "first@example.com",
|
||||
megaPassword: "first-pass",
|
||||
megaDebridApiEnabled: true,
|
||||
megaDebridWebEnabled: true,
|
||||
megaDebridPreferApi: false,
|
||||
megaDebridDisabledAccountIds: [oldId, firstId],
|
||||
megaDebridAccountDailyLimitBytes: { [oldId]: 15 * GIB, [firstId]: 9 * GIB },
|
||||
megaDebridAccountDailyUsageBytes: { [oldId]: 4 * GIB, [firstId]: 2 * GIB },
|
||||
megaDebridAccountTotalUsageBytes: { [oldId]: 40 * GIB, [firstId]: 20 * GIB },
|
||||
debridAccountStatuses: {
|
||||
[oldId]: { accountId: oldId, provider: "megadebrid" as const, label: "Account 2", maskedLogin: "se***om", valid: true, isPremium: true, premiumUntilMs: null, message: "OK", checkedAt: 1 },
|
||||
[firstId]: { accountId: firstId, provider: "megadebrid" as const, label: "Account 1", maskedLogin: "fi***om", valid: true, isPremium: true, premiumUntilMs: null, message: "OK", checkedAt: 1 }
|
||||
}
|
||||
};
|
||||
const state = {
|
||||
...createAccountEditState(megaTarget("second@example.com"), settings),
|
||||
login: "renamed@example.com",
|
||||
password: "new-pass",
|
||||
dailyLimitGb: "25,5"
|
||||
};
|
||||
|
||||
expect(validateAccountEdit(state, settings)).toBeNull();
|
||||
const next = applyAccountEdit(settings, state);
|
||||
|
||||
expect(next.megaCredentials).toBe("first@example.com:first-pass\nrenamed@example.com:new-pass\nthird@example.com:third-pass");
|
||||
expect(next.megaLogin).toBe("first@example.com");
|
||||
expect(next.megaPassword).toBe("first-pass");
|
||||
expect(next.megaDebridApiEnabled).toBe(true);
|
||||
expect(next.megaDebridWebEnabled).toBe(true);
|
||||
expect(next.megaDebridPreferApi).toBe(false);
|
||||
expect(next.megaDebridDisabledAccountIds).toEqual([firstId, newId]);
|
||||
expect(next.megaDebridAccountDailyLimitBytes).toEqual({ [firstId]: 9 * GIB, [newId]: Math.floor(25.5 * GIB) });
|
||||
expect(next.megaDebridAccountDailyUsageBytes).toEqual({ [firstId]: 2 * GIB });
|
||||
expect(next.megaDebridAccountTotalUsageBytes).toEqual({ [firstId]: 20 * GIB });
|
||||
expect(next.debridAccountStatuses).toEqual({
|
||||
[firstId]: { accountId: firstId, provider: "megadebrid", label: "Account 1", maskedLogin: "fi***om", valid: true, isPremium: true, premiumUntilMs: null, message: "OK", checkedAt: 1 }
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps Mega-Debrid usage when only the password changes", () => {
|
||||
const id = getMegaDebridAccountId("user@example.com");
|
||||
const settings = {
|
||||
...defaultSettings(),
|
||||
megaCredentials: "user@example.com:old-pass",
|
||||
megaLogin: "user@example.com",
|
||||
megaPassword: "old-pass",
|
||||
megaDebridAccountDailyLimitBytes: { [id]: 8 * GIB },
|
||||
megaDebridAccountDailyUsageBytes: { [id]: 3 * GIB },
|
||||
megaDebridAccountTotalUsageBytes: { [id]: 33 * GIB }
|
||||
};
|
||||
const state = { ...createAccountEditState(megaTarget("user@example.com"), settings), password: "new-pass" };
|
||||
const next = applyAccountEdit(settings, state);
|
||||
|
||||
expect(next.megaCredentials).toBe("user@example.com:new-pass");
|
||||
expect(next.megaDebridAccountDailyUsageBytes).toEqual({ [id]: 3 * GIB });
|
||||
expect(next.megaDebridAccountTotalUsageBytes).toEqual({ [id]: 33 * GIB });
|
||||
});
|
||||
|
||||
it("preserves exact account limits when the rounded display value is left unchanged", () => {
|
||||
const megaLogin = "user@example.com";
|
||||
const megaId = getMegaDebridAccountId(megaLogin);
|
||||
const key = "debrid-link-token";
|
||||
const keyId = getDebridLinkApiKeyId(key);
|
||||
const exactLimit = Math.floor(10.05 * GIB);
|
||||
const settings = {
|
||||
...defaultSettings(),
|
||||
megaCredentials: `${megaLogin}:pass`,
|
||||
megaLogin,
|
||||
megaPassword: "pass",
|
||||
megaDebridAccountDailyLimitBytes: { [megaId]: exactLimit },
|
||||
debridLinkApiKeys: key,
|
||||
debridLinkApiKeyDailyLimitBytes: { [keyId]: exactLimit }
|
||||
};
|
||||
|
||||
const megaNext = applyAccountEdit(settings, createAccountEditState(megaTarget(megaLogin), settings));
|
||||
const debridLinkNext = applyAccountEdit(settings, createAccountEditState(debridLinkTarget(key), settings));
|
||||
|
||||
expect(megaNext.megaDebridAccountDailyLimitBytes[megaId]).toBe(exactLimit);
|
||||
expect(debridLinkNext.debridLinkApiKeyDailyLimitBytes[keyId]).toBe(exactLimit);
|
||||
});
|
||||
|
||||
it("rejects whitespace-only Mega-Debrid passwords and empty or mismatched check results", () => {
|
||||
const login = "user@example.com";
|
||||
const settings = {
|
||||
...defaultSettings(),
|
||||
megaCredentials: `${login}:pass`,
|
||||
megaLogin: login,
|
||||
megaPassword: "pass"
|
||||
};
|
||||
const state = { ...createAccountEditState(megaTarget(login), settings), password: " " };
|
||||
|
||||
expect(validateAccountEdit(state, settings)).toMatch(/Passwort/i);
|
||||
expect(validateAccountEditStatuses(state, [])).toMatch(/keinen Account/i);
|
||||
expect(validateAccountEditStatuses(state, [{
|
||||
accountId: "mda_other",
|
||||
provider: "megadebrid",
|
||||
label: "Account 1",
|
||||
maskedLogin: "ot***er",
|
||||
valid: true,
|
||||
isPremium: true,
|
||||
premiumUntilMs: null,
|
||||
message: "OK",
|
||||
checkedAt: 1
|
||||
}])).toMatch(/falschen Account/i);
|
||||
});
|
||||
|
||||
it("rejects duplicate Mega-Debrid logins and missing row targets", () => {
|
||||
const settings = {
|
||||
...defaultSettings(),
|
||||
megaCredentials: "first@example.com:first-pass\nsecond@example.com:second-pass"
|
||||
};
|
||||
const duplicate = {
|
||||
...createAccountEditState(megaTarget("second@example.com"), settings),
|
||||
login: "first@example.com"
|
||||
};
|
||||
|
||||
expect(validateAccountEdit(duplicate, settings)).toMatch(/bereits vorhanden/i);
|
||||
expect(() => createAccountEditState(megaTarget("missing@example.com"), settings)).toThrow(/nicht gefunden/i);
|
||||
});
|
||||
|
||||
it("replaces only the selected Debrid-Link key and migrates its own metadata", () => {
|
||||
const keyA = "token-a";
|
||||
const keyB = "token-b";
|
||||
const keyC = "token-c";
|
||||
const newKey = "token-b-new";
|
||||
const idA = getDebridLinkApiKeyId(keyA);
|
||||
const idB = getDebridLinkApiKeyId(keyB);
|
||||
const idNew = getDebridLinkApiKeyId(newKey);
|
||||
const settings = {
|
||||
...defaultSettings(),
|
||||
debridLinkApiKeys: `${keyA}\n${keyB}\n${keyC}`,
|
||||
debridLinkDisabledKeyIds: [idB, idA],
|
||||
debridLinkApiKeyDailyLimitBytes: { [idA]: 5 * GIB, [idB]: 10 * GIB },
|
||||
debridLinkApiKeyDailyUsageBytes: { [idA]: 2 * GIB, [idB]: 4 * GIB },
|
||||
debridLinkApiKeyTotalUsageBytes: { [idA]: 12 * GIB, [idB]: 24 * GIB }
|
||||
};
|
||||
const state = {
|
||||
...createAccountEditState(debridLinkTarget(keyB), settings),
|
||||
token: newKey,
|
||||
dailyLimitGb: "12"
|
||||
};
|
||||
const next = applyAccountEdit(settings, state);
|
||||
|
||||
expect(next.debridLinkApiKeys).toBe(`${keyA}\n${newKey}\n${keyC}`);
|
||||
expect(next.debridLinkDisabledKeyIds).toEqual([idA, idNew]);
|
||||
expect(next.debridLinkApiKeyDailyLimitBytes).toEqual({ [idA]: 5 * GIB, [idNew]: 12 * GIB });
|
||||
expect(next.debridLinkApiKeyDailyUsageBytes).toEqual({ [idA]: 2 * GIB });
|
||||
expect(next.debridLinkApiKeyTotalUsageBytes).toEqual({ [idA]: 12 * GIB });
|
||||
});
|
||||
|
||||
it("edits a single login without changing unrelated credentials", () => {
|
||||
const settings = {
|
||||
...defaultSettings(),
|
||||
ddownloadLogin: "old@example.com",
|
||||
ddownloadPassword: "old-pass",
|
||||
linkSnappyLogin: "keep@example.com",
|
||||
linkSnappyPassword: "keep-pass"
|
||||
};
|
||||
const target: AccountEditTarget = {
|
||||
type: "single",
|
||||
rowKey: "svc-ddownload",
|
||||
kind: "ddownload-login",
|
||||
service: "ddownload",
|
||||
provider: "ddownload"
|
||||
};
|
||||
const state = {
|
||||
...createAccountEditState(target, settings),
|
||||
login: "new@example.com",
|
||||
password: "new-pass",
|
||||
dailyLimitGb: "7"
|
||||
};
|
||||
const next = applyAccountEdit(settings, state);
|
||||
|
||||
expect(next.ddownloadLogin).toBe("new@example.com");
|
||||
expect(next.ddownloadPassword).toBe("new-pass");
|
||||
expect(next.linkSnappyLogin).toBe("keep@example.com");
|
||||
expect(next.linkSnappyPassword).toBe("keep-pass");
|
||||
expect(next.providerDailyLimitBytes.ddownload).toBe(7 * GIB);
|
||||
});
|
||||
|
||||
it("rejects whitespace-only passwords for direct login accounts", () => {
|
||||
const settings = {
|
||||
...defaultSettings(),
|
||||
ddownloadLogin: "user@example.com",
|
||||
ddownloadPassword: "password",
|
||||
linkSnappyLogin: "member@example.com",
|
||||
linkSnappyPassword: "secret"
|
||||
};
|
||||
const ddownloadTarget: AccountEditTarget = {
|
||||
type: "single",
|
||||
rowKey: "svc-ddownload",
|
||||
kind: "ddownload-login",
|
||||
service: "ddownload",
|
||||
provider: "ddownload"
|
||||
};
|
||||
const linkSnappyTarget: AccountEditTarget = {
|
||||
type: "single",
|
||||
rowKey: "svc-linksnappy",
|
||||
kind: "linksnappy-login",
|
||||
service: "linksnappy",
|
||||
provider: "linksnappy"
|
||||
};
|
||||
|
||||
expect(validateAccountEdit({ ...createAccountEditState(ddownloadTarget, settings), password: " " }, settings)).toMatch(/Passwort/i);
|
||||
expect(validateAccountEdit({ ...createAccountEditState(linkSnappyTarget, settings), password: "\t" }, settings)).toMatch(/Passwort/i);
|
||||
});
|
||||
|
||||
it("builds a targeted check snapshot without invalid sibling accounts", () => {
|
||||
const settings = {
|
||||
...defaultSettings(),
|
||||
megaCredentials: "first@example.com:first-pass\nsecond@example.com:second-pass",
|
||||
megaLogin: "first@example.com",
|
||||
megaPassword: "first-pass",
|
||||
debridLinkApiKeys: "one\ntwo"
|
||||
};
|
||||
const target = megaTarget("second@example.com");
|
||||
const state = createAccountEditState(target, settings);
|
||||
const checkSettings = buildAccountEditCheckSettings(settings, state);
|
||||
|
||||
expect(checkSettings.megaCredentials).toBe("second@example.com:second-pass");
|
||||
expect(checkSettings.megaLogin).toBe("second@example.com");
|
||||
expect(checkSettings.megaPassword).toBe("second-pass");
|
||||
expect(checkSettings.debridLinkApiKeys).toBe("");
|
||||
});
|
||||
|
||||
it("removes only the selected Mega-Debrid account and all metadata belonging to it", () => {
|
||||
const removeId = getMegaDebridAccountId("second@example.com");
|
||||
const keepId = getMegaDebridAccountId("first@example.com");
|
||||
const settings = {
|
||||
...defaultSettings(),
|
||||
megaCredentials: "first@example.com:first-pass\nsecond@example.com:second-pass",
|
||||
megaLogin: "first@example.com",
|
||||
megaPassword: "first-pass",
|
||||
megaDebridDisabledAccountIds: [removeId, keepId],
|
||||
megaDebridAccountDailyLimitBytes: { [removeId]: 2, [keepId]: 1 },
|
||||
megaDebridAccountDailyUsageBytes: { [removeId]: 4, [keepId]: 3 },
|
||||
megaDebridAccountTotalUsageBytes: { [removeId]: 6, [keepId]: 5 },
|
||||
debridAccountStatuses: {
|
||||
[removeId]: { accountId: removeId, provider: "megadebrid" as const, label: "Account 2", maskedLogin: "se***om", valid: true, isPremium: false, premiumUntilMs: null, message: "Free", checkedAt: 1 },
|
||||
[keepId]: { accountId: keepId, provider: "megadebrid" as const, label: "Account 1", maskedLogin: "fi***om", valid: true, isPremium: true, premiumUntilMs: null, message: "OK", checkedAt: 1 }
|
||||
}
|
||||
};
|
||||
const next = removeAccountTarget(settings, megaTarget("second@example.com"));
|
||||
|
||||
expect(next.megaCredentials).toBe("first@example.com:first-pass");
|
||||
expect(next.megaDebridDisabledAccountIds).toEqual([keepId]);
|
||||
expect(next.megaDebridAccountDailyLimitBytes).toEqual({ [keepId]: 1 });
|
||||
expect(next.megaDebridAccountDailyUsageBytes).toEqual({ [keepId]: 3 });
|
||||
expect(next.megaDebridAccountTotalUsageBytes).toEqual({ [keepId]: 5 });
|
||||
expect(next.debridAccountStatuses).toEqual({
|
||||
[keepId]: { accountId: keepId, provider: "megadebrid", label: "Account 1", maskedLogin: "fi***om", valid: true, isPremium: true, premiumUntilMs: null, message: "OK", checkedAt: 1 }
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { ACCOUNT_SERVICE_ICONS } from "../src/renderer/account-service-icons";
|
||||
|
||||
const services = [
|
||||
"realdebrid",
|
||||
"megadebrid-api",
|
||||
"megadebrid-web",
|
||||
"bestdebrid",
|
||||
"alldebrid",
|
||||
"ddownload",
|
||||
"onefichier",
|
||||
"debridlink",
|
||||
"linksnappy"
|
||||
] as const;
|
||||
|
||||
describe("account service icons", () => {
|
||||
it("bundles a valid local PNG or ICO for every account service", async () => {
|
||||
expect(Object.keys(ACCOUNT_SERVICE_ICONS).sort()).toEqual([...services].sort());
|
||||
for (const service of services) {
|
||||
const relativePath = ACCOUNT_SERVICE_ICONS[service];
|
||||
expect(relativePath.startsWith("./provider-icons/")).toBe(true);
|
||||
const bytes = await readFile(path.resolve("assets", relativePath.slice(2)));
|
||||
const png = bytes.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]));
|
||||
const ico = bytes.subarray(0, 4).equals(Buffer.from([0x00, 0x00, 0x01, 0x00]));
|
||||
expect(png || ico).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("uses the same Mega-Debrid icon for API and Web accounts", () => {
|
||||
expect(ACCOUNT_SERVICE_ICONS["megadebrid-api"]).toBe(ACCOUNT_SERVICE_ICONS["megadebrid-web"]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { canPersistExpectedAccountStatus } from "../src/main/account-status-persistence";
|
||||
import type { DebridAccountStatus } from "../src/shared/types";
|
||||
|
||||
function status(accountId: string, valid = true): DebridAccountStatus {
|
||||
return {
|
||||
accountId,
|
||||
provider: "megadebrid",
|
||||
label: "Account 1",
|
||||
maskedLogin: "us***om",
|
||||
valid,
|
||||
isPremium: true,
|
||||
premiumUntilMs: null,
|
||||
message: valid ? "OK" : "Ungültig",
|
||||
checkedAt: 1
|
||||
};
|
||||
}
|
||||
|
||||
describe("account status persistence", () => {
|
||||
it("accepts only one valid status for the expected account", () => {
|
||||
expect(canPersistExpectedAccountStatus([status("expected")], "expected")).toBe(true);
|
||||
expect(canPersistExpectedAccountStatus([], "expected")).toBe(false);
|
||||
expect(canPersistExpectedAccountStatus([status("other")], "expected")).toBe(false);
|
||||
expect(canPersistExpectedAccountStatus([status("expected", false)], "expected")).toBe(false);
|
||||
expect(canPersistExpectedAccountStatus([status("expected"), status("other")], "expected")).toBe(false);
|
||||
expect(canPersistExpectedAccountStatus([status("expected")], "")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
isAccountRowSelectionKey,
|
||||
matchesAccountModeFilter,
|
||||
pruneAccountRowSelection,
|
||||
resolveAccountUsername,
|
||||
resolveVisibleAccountKind
|
||||
} from "../src/renderer/account-ui";
|
||||
|
||||
@@ -89,3 +90,11 @@ describe("account selection", () => {
|
||||
expect(isAccountRowSelectionKey(" ", false)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("account usernames", () => {
|
||||
it("shows the full stored login and prefers a checked provider email", () => {
|
||||
expect(resolveAccountUsername("member@example.com", undefined)).toBe("member@example.com");
|
||||
expect(resolveAccountUsername("stored@example.com", "verified@example.com")).toBe("verified@example.com");
|
||||
expect(resolveAccountUsername("", undefined)).toBe("—");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,6 +41,7 @@ const { verifyPublicRelease, verifyReleaseArchives } = await import(verifierUrl)
|
||||
const fixtureRoots: string[] = [];
|
||||
const redistributionFiles = [
|
||||
"LICENSE",
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"resources/extractor-jvm/licenses/LGPL-2.1.txt",
|
||||
"resources/extractor-jvm/licenses/7-Zip-license.txt",
|
||||
"resources/extractor-jvm/licenses/Apache-2.0.txt",
|
||||
@@ -59,6 +60,8 @@ function writeRedistributionFiles(rootDir: string, packaged = false): void {
|
||||
let targetPath: string = relativePath;
|
||||
if (packaged && relativePath === "LICENSE") {
|
||||
targetPath = "win-unpacked/resources/LICENSE";
|
||||
} else if (packaged && relativePath === "THIRD_PARTY_NOTICES.md") {
|
||||
targetPath = "win-unpacked/resources/THIRD_PARTY_NOTICES.md";
|
||||
} else if (packaged) {
|
||||
targetPath = `win-unpacked/resources/app.asar.unpacked/${relativePath}`;
|
||||
}
|
||||
@@ -74,6 +77,8 @@ function writeArchivePayload(outputDir: string, omittedName = ""): void {
|
||||
const content = fs.readFileSync(path.resolve(...relativePath.split("/")));
|
||||
const targetPath = relativePath === "LICENSE"
|
||||
? "resources/LICENSE"
|
||||
: relativePath === "THIRD_PARTY_NOTICES.md"
|
||||
? "resources/THIRD_PARTY_NOTICES.md"
|
||||
: `resources/app.asar.unpacked/${relativePath}`;
|
||||
writeFile(outputDir, targetPath, content);
|
||||
}
|
||||
@@ -117,12 +122,17 @@ function createReleaseFixture(): string {
|
||||
"build/renderer/**/*",
|
||||
"resources/extractor-jvm/**/*",
|
||||
"LICENSE",
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"package.json"
|
||||
],
|
||||
extraResources: [
|
||||
{
|
||||
from: "LICENSE",
|
||||
to: "LICENSE"
|
||||
},
|
||||
{
|
||||
from: "THIRD_PARTY_NOTICES.md",
|
||||
to: "THIRD_PARTY_NOTICES.md"
|
||||
}
|
||||
],
|
||||
nsis: {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { defaultSettings } from "../src/main/constants";
|
||||
import { overlayLiveUsageCounters } from "../src/main/settings-live-overlay";
|
||||
import { getDebridLinkApiKeyId } from "../src/shared/debrid-link-keys";
|
||||
import { getMegaDebridAccountId } from "../src/shared/mega-debrid-accounts";
|
||||
|
||||
describe("live settings overlay", () => {
|
||||
it("keeps current Mega-Debrid counters and drops data for identities no longer configured", () => {
|
||||
const keepMegaId = getMegaDebridAccountId("keep@example.com");
|
||||
const removedMegaId = getMegaDebridAccountId("removed@example.com");
|
||||
const keepKeyId = getDebridLinkApiKeyId("keep-key");
|
||||
const removedKeyId = getDebridLinkApiKeyId("removed-key");
|
||||
const target = {
|
||||
...defaultSettings(),
|
||||
megaCredentials: "keep@example.com:pass",
|
||||
megaLogin: "keep@example.com",
|
||||
megaPassword: "pass",
|
||||
debridLinkApiKeys: "keep-key",
|
||||
megaDebridAccountDailyUsageBytes: { [keepMegaId]: 1 },
|
||||
megaDebridAccountTotalUsageBytes: { [keepMegaId]: 2 }
|
||||
};
|
||||
const live = {
|
||||
...defaultSettings(),
|
||||
megaCredentials: "keep@example.com:pass\nremoved@example.com:pass",
|
||||
debridLinkApiKeys: "keep-key\nremoved-key",
|
||||
megaDebridAccountDailyUsageBytes: { [keepMegaId]: 300, [removedMegaId]: 400 },
|
||||
megaDebridAccountTotalUsageBytes: { [keepMegaId]: 3_000, [removedMegaId]: 4_000 },
|
||||
debridLinkApiKeyDailyUsageBytes: { [keepKeyId]: 500, [removedKeyId]: 600 },
|
||||
debridLinkApiKeyTotalUsageBytes: { [keepKeyId]: 5_000, [removedKeyId]: 6_000 },
|
||||
debridAccountStatuses: {
|
||||
[keepMegaId]: { accountId: keepMegaId, provider: "megadebrid" as const, label: "Account 1", maskedLogin: "ke***om", valid: true, isPremium: true, premiumUntilMs: null, message: "OK", checkedAt: 1 },
|
||||
[removedMegaId]: { accountId: removedMegaId, provider: "megadebrid" as const, label: "Account 2", maskedLogin: "re***om", valid: true, isPremium: true, premiumUntilMs: null, message: "OK", checkedAt: 1 },
|
||||
[keepKeyId]: { accountId: keepKeyId, provider: "debridlink" as const, label: "Key 1", maskedLogin: "kee***key", valid: true, isPremium: false, premiumUntilMs: null, message: "Free", checkedAt: 1 },
|
||||
[removedKeyId]: { accountId: removedKeyId, provider: "debridlink" as const, label: "Key 2", maskedLogin: "rem***key", valid: true, isPremium: false, premiumUntilMs: null, message: "Free", checkedAt: 1 }
|
||||
}
|
||||
};
|
||||
|
||||
overlayLiveUsageCounters(target, live, 9_000);
|
||||
|
||||
expect(target.megaDebridAccountDailyUsageBytes).toEqual({ [keepMegaId]: 300 });
|
||||
expect(target.megaDebridAccountTotalUsageBytes).toEqual({ [keepMegaId]: 3_000 });
|
||||
expect(target.debridLinkApiKeyDailyUsageBytes).toEqual({ [keepKeyId]: 500 });
|
||||
expect(target.debridLinkApiKeyTotalUsageBytes).toEqual({ [keepKeyId]: 5_000 });
|
||||
expect(Object.keys(target.debridAccountStatuses).sort()).toEqual([keepKeyId, keepMegaId].sort());
|
||||
expect(target.totalRuntimeAllTimeMs).toBe(9_000);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user