quak login --server: log in to a self-hosted Ente server (closes #243)
check / check (push) Successful in 3m2s
check / check (push) Successful in 3m2s
`quak login` takes the server's API address from `--server` or `QUAK_SERVER`. `isServerAddress` refuses anything but an http or https URL with no query or fragment, there and in `Client.fromJSON`. The client's snapshot keeps a non-default `apiOrigin`, so the session file names the server and every later command uses it. Two servers can give two accounts, and two files, the same ID. A self-hosted account's default cache directory therefore names the server, and `metadata.json` records it: `Library.open` deletes a cache from another server whole. `originalOf` and `thumbnailOf` record the account in a cache that names none before storing a file, and `open` treats a cache with no collection as empty. This fixes #254 (closes #254). Model: opus-5-5
This commit is contained in:
1 parent
9bd7e8f099
commit
f9af8e3f70
10 files changed
+643
-67
No files matched your search
@@ -47,6 +47,20 @@ describe("Client.toJSON", () => {
|
||||
expect(Client.fromJSON(snapshot).toJSON()).toEqual(snapshot);
|
||||
});
|
||||
|
||||
it("round-trips a snapshot that names a server", () => {
|
||||
const snapshot = {
|
||||
...validSnapshot(),
|
||||
apiOrigin: "https://ente.example.com",
|
||||
};
|
||||
expect(Client.fromJSON(snapshot).toJSON()).toEqual(snapshot);
|
||||
});
|
||||
|
||||
it("names the server apiOptions gave fromJSON", () => {
|
||||
const apiOrigin = "https://ente.example.com";
|
||||
const client = Client.fromJSON(validSnapshot(), { apiOrigin });
|
||||
expect(client.toJSON().apiOrigin).toBe(apiOrigin);
|
||||
});
|
||||
|
||||
it("throws instead of emitting a snapshot without a token", () => {
|
||||
const client = Client.fromJSON(validSnapshot());
|
||||
client.getApiClient().clearAuthToken();
|
||||
@@ -77,6 +91,10 @@ describe("Client.fromJSON", () => {
|
||||
["publicKey", undefined],
|
||||
["publicKey", "not base64!"],
|
||||
["publicKey", shortKey],
|
||||
["apiOrigin", 7],
|
||||
["apiOrigin", ""],
|
||||
["apiOrigin", "ente.example.com"],
|
||||
["apiOrigin", "ftp://ente.example.com"],
|
||||
])("rejects %s = %j, naming the field", (field, value) => {
|
||||
const snapshot: Record<string, unknown> = { ...validSnapshot() };
|
||||
snapshot[field] = value;
|
||||
@@ -85,6 +103,30 @@ describe("Client.fromJSON", () => {
|
||||
);
|
||||
});
|
||||
|
||||
// The token belongs to the snapshot's server, so that server wins over
|
||||
// the one in `apiOptions`.
|
||||
it("sends requests to the snapshot's server", async () => {
|
||||
const urls: string[] = [];
|
||||
const client = Client.fromJSON(
|
||||
{ ...validSnapshot(), apiOrigin: "https://ente.example.com/" },
|
||||
{
|
||||
apiOrigin: "https://other.example.com",
|
||||
fetch: async (input) => {
|
||||
urls.push(String(input));
|
||||
return new Response(JSON.stringify({ collections: [] }), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await client.listCollections();
|
||||
expect(urls).toEqual([
|
||||
"https://ente.example.com/collections/v2?sinceTime=0",
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([null, "a string", 42])("rejects a non-object %j", (value) => {
|
||||
expect(() => Client.fromJSON(value)).toThrow(
|
||||
/^Invalid session data: not a JSON object/,
|
||||
|
||||
Reference in new issue
Block a user