Changes
3 changed files (+297/-0)
-
-
@@ -0,0 +1,181 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import "fake-indexeddb/auto"; import { beforeEach, expect, test } from "bun:test"; import { create, fromBinary, toBinary } from "@bufbuild/protobuf"; import { CreateRequestSchema } from "@yamori/proto/yamori/workspace/v1/create_request_pb.js"; import { CreateResponseSchema } from "@yamori/proto/yamori/workspace/v1/create_response_pb.js"; import { GetRequestSchema } from "@yamori/proto/yamori/workspace/v1/get_request_pb.js"; import { GetResponseSchema } from "@yamori/proto/yamori/workspace/v1/get_response_pb.js"; import { WorkspaceSchema } from "@yamori/proto/yamori/workspace/v1/workspace_pb.js"; import { IDBFactory } from "fake-indexeddb"; import { openDB } from "idb"; import { idbBackend } from "../../../../lib"; import { CONTEXT } from "../../../../symbols"; import { create as createHandler } from "./create"; import { get } from "./get"; beforeEach(() => { indexedDB = new IDBFactory(); }); test("Should return an error response on DB error", async () => { const resp = fromBinary( GetResponseSchema, await get( toBinary( GetRequestSchema, create(GetRequestSchema, { workspaceId: { value: "ws-foo", }, }), ), { db: await openDB("test"), }, ), ); if (resp.result.case !== "systemError") { expect.unreachable(); } expect(resp.result.value.code).toBe("IDB_ERROR"); }); test("Should return workspace matches to the ID", async () => { const { [CONTEXT]: ctx } = await idbBackend(); const add = async (displayName: string) => { return fromBinary( CreateResponseSchema, await createHandler( toBinary( CreateRequestSchema, create(CreateRequestSchema, { displayName, }), ), ctx, ), ); }; const foo = await add("Foo"); if (foo.result.case !== "ok") { expect.unreachable(); } const bar = await add("Bar"); if (bar.result.case !== "ok") { expect.unreachable(); } const resp = fromBinary( GetResponseSchema, await get( toBinary( GetRequestSchema, create(GetRequestSchema, { workspaceId: foo.result.value.workspace?.id, }), ), ctx, ), ); if (resp.result.case !== "ok") { expect.unreachable(); } expect(resp.result.value.id).toEqual(foo.result.value.workspace!.id!); expect(resp.result.value.displayName).toBe("Foo"); expect(resp.result.value.updateKey?.key).toBeInstanceOf(Uint8Array); expect(resp.result.value.deletionKey?.key).toBeInstanceOf(Uint8Array); expect(resp.result.value.workerAddKey?.key).toBeInstanceOf(Uint8Array); }); test("Should mask response", async () => { const { [CONTEXT]: ctx } = await idbBackend(); const add = async (displayName: string) => { return fromBinary( CreateResponseSchema, await createHandler( toBinary( CreateRequestSchema, create(CreateRequestSchema, { displayName, }), ), ctx, ), ); }; const foo = await add("Foo"); if (foo.result.case !== "ok") { expect.unreachable(); } const resp1 = fromBinary( GetResponseSchema, await get( toBinary( GetRequestSchema, create(GetRequestSchema, { workspaceId: foo.result.value.workspace?.id, readMask: { fields: [WorkspaceSchema.field.displayName.number], }, }), ), ctx, ), ); if (resp1.result.case !== "ok") { expect.unreachable(); } expect(resp1.result.value.id).toBeEmpty(); expect(resp1.result.value.displayName).toBe("Foo"); expect(resp1.result.value.updateKey).toBeEmpty(); expect(resp1.result.value.deletionKey).toBeEmpty(); expect(resp1.result.value.workerAddKey).toBeEmpty(); const resp2 = fromBinary( GetResponseSchema, await get( toBinary( GetRequestSchema, create(GetRequestSchema, { workspaceId: foo.result.value.workspace?.id, readMask: { fields: [ WorkspaceSchema.field.id.number, WorkspaceSchema.field.deletionKey.number, ], }, }), ), ctx, ), ); if (resp2.result.case !== "ok") { expect.unreachable(); } expect(resp2.result.value.id).toEqual(foo.result.value.workspace!.id!); expect(resp2.result.value.displayName).toBeEmpty(); expect(resp2.result.value.updateKey).toBeEmpty(); expect(resp2.result.value.deletionKey?.key).toBeInstanceOf(Uint8Array); expect(resp2.result.value.workerAddKey).toBeEmpty(); });
-
-
-
@@ -0,0 +1,113 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { create, fromBinary, toBinary } from "@bufbuild/protobuf"; import { GetRequestSchema, type GetRequest, } from "@yamori/proto/yamori/workspace/v1/get_request_pb.js"; import { GetResponseSchema } from "@yamori/proto/yamori/workspace/v1/get_response_pb.js"; import { WorkspaceSchema } from "@yamori/proto/yamori/workspace/v1/workspace_pb.js"; import { type Context, type YamoriDB } from "../../../../types"; export async function get(data: Uint8Array, { db }: Context): Promise<Uint8Array> { let req: GetRequest; try { req = fromBinary(GetRequestSchema, data); } catch (error) { return toBinary( GetResponseSchema, create(GetResponseSchema, { result: { case: "systemError", value: { code: "INVALID_MESSAGE", message: import.meta.env.NODE_ENV === "production" ? "Failed to parse request message" : `Request message does not conform schema: ${error}`, }, }, }), ); } if (!req.workspaceId?.value) { return toBinary( GetResponseSchema, create(GetResponseSchema, { result: { case: "missingField", value: { path: "workspace_id", }, }, }), ); } let found: YamoriDB["workspaces"]["value"]; try { const entry = await db.get("workspaces", req.workspaceId.value); if (!entry) { return toBinary( GetResponseSchema, create(GetResponseSchema, { result: { case: "notFound", value: { typeName: "yamori.workspace.v1.Workspace", }, }, }), ); } found = entry; } catch (error) { return toBinary( GetResponseSchema, create(GetResponseSchema, { result: { case: "systemError", value: { code: "IDB_ERROR", message: import.meta.env.NODE_ENV === "production" ? "Failed to query workspace" : `Exception thrown during get operation: ${error}`, }, }, }), ); } const shouldReturn = (fieldNumber: number): true | undefined => !req.readMask || req.readMask.fields.includes(fieldNumber) || undefined; return toBinary( GetResponseSchema, create(GetResponseSchema, { result: { case: "ok", value: { id: shouldReturn(WorkspaceSchema.field.id.number) && { value: found.id, }, displayName: shouldReturn(WorkspaceSchema.field.displayName.number) && found.displayName, updateKey: shouldReturn(WorkspaceSchema.field.updateKey.number) && { key: found.capabilities.updateKey, }, deletionKey: shouldReturn(WorkspaceSchema.field.deletionKey.number) && { key: found.capabilities.deletionKey, }, workerAddKey: shouldReturn(WorkspaceSchema.field.workerAddKey.number) && { key: found.capabilities.workerAddKey, }, }, }, }), ); }
-
-
-
@@ -5,6 +5,7 @@ import { type Context, type RPCMessage } from "../../../../types";import { create } from "./create"; import { deleteWorkspace } from "./delete"; import { get } from "./get"; import { list } from "./list"; import { update } from "./update";
-
@@ -21,6 +22,8 @@ case "Update":return update(request.data, ctx); case "Delete": return deleteWorkspace(request.data, ctx); case "Get": return get(request.data, ctx); default: throw new Error(`Unknown method "${request.method}" for ${request.service}`); }
-