상위 디렉터리 및 카테고리의 소유권을 검증하지 않던 문제 등 버그 수정 및 Vitest 기반 테스트 추가

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
staticandClaude Opus 5.5 committed 2026-10-11 23:45:56 +09:00
1 parent 67989f4741
commit ed732be999
31 files changed
+2057 -42

No files matched your search

+52
View File
@@ -0,0 +1,52 @@
import { mkdtemp, writeFile } from "fs/promises";
import { tmpdir } from "os";
import { join } from "path";
import { buffer } from "stream/consumers";
import type { ReadableStream } from "stream/web";
import { describe, expect, it } from "vitest";
import { createEncContentStream } from "#lib/server/services/file.js";
const createFile = async (content: string) => {
const path = join(await mkdtemp(join(tmpdir(), "arkvault-test-")), "file");
await writeFile(path, content);
return path;
};
const read = async (stream: unknown) => (await buffer(stream as ReadableStream)).toString();
describe("createEncContentStream", () => {
it("serves the whole content when no range is requested", async () => {
const path = await createFile("0123456789");
const { encContentStream, range, isRangeRequest } = await createEncContentStream(path);
expect(isRangeRequest).toBe(false);
expect(range).toEqual({ start: 0, end: 9, total: 10 });
expect(await read(encContentStream)).toBe("0123456789");
});
it("serves the requested range", async () => {
const path = await createFile("0123456789");
const { encContentStream, range, isRangeRequest } = await createEncContentStream(
path,
undefined,
{ start: 2, end: 5 },
);
expect(isRangeRequest).toBe(true);
expect(range).toEqual({ start: 2, end: 5, total: 10 });
expect(await read(encContentStream)).toBe("2345");
});
it("prepends the legacy IV to the content", async () => {
const path = await createFile("6789");
const { encContentStream } = await createEncContentStream(path, Buffer.from("012345"), {
start: 4,
end: 7,
});
expect(await read(encContentStream)).toBe("4567");
});
});
+64
View File
@@ -0,0 +1,64 @@
import { beforeEach, describe, expect, it } from "vitest";
import { CategoryRepo, DirectoryRepo, UploadRepo, IntegrityError } from "#lib/server/db/index.js";
import {
createCategory,
createDirectory,
createUser,
newEntryParams,
resetDatabase,
ciphertext,
} from "../helpers/database";
beforeEach(resetDatabase);
describe("parent ownership", () => {
it("allows creating a directory under the user's own directory", async () => {
const { userId } = await createUser();
const parentId = await createDirectory(userId);
await DirectoryRepo.registerDirectory(newEntryParams(userId, parentId));
const directories = await DirectoryRepo.getAllDirectoriesByParent(userId, parentId);
expect(directories).toHaveLength(1);
});
it("rejects creating a directory under another user's directory", async () => {
const { userId: victimId } = await createUser();
const { userId: attackerId } = await createUser();
const victimDirectoryId = await createDirectory(victimId);
await expect(
DirectoryRepo.registerDirectory(newEntryParams(attackerId, victimDirectoryId)),
).rejects.toThrow(new IntegrityError("Directory not found"));
});
it("rejects creating a category under another user's category", async () => {
const { userId: victimId } = await createUser();
const { userId: attackerId } = await createUser();
const victimCategoryId = await createCategory(victimId);
await expect(
CategoryRepo.registerCategory(newEntryParams(attackerId, victimCategoryId)),
).rejects.toThrow(new IntegrityError("Category not found"));
});
it("rejects uploading a file into another user's directory", async () => {
const { userId: victimId } = await createUser();
const { userId: attackerId } = await createUser();
const victimDirectoryId = await createDirectory(victimId);
await expect(
UploadRepo.createFileUploadSession({
...newEntryParams(attackerId, victimDirectoryId),
id: crypto.randomUUID(),
path: "unused",
totalChunks: 1,
expiresAt: new Date(Date.now() + 60_000),
hskVersion: null,
contentType: "image/png",
encCreatedAt: null,
encLastModifiedAt: ciphertext(),
}),
).rejects.toThrow(new IntegrityError("Directory not found"));
});
});
+35
View File
@@ -0,0 +1,35 @@
import { beforeEach, describe, expect, it } from "vitest";
import { UploadRepo } from "#lib/server/db/index.js";
import { isChunkUploaded } from "#lib/server/services/upload.js";
import { ciphertext, createUser, newEntryParams, resetDatabase } from "../helpers/database";
beforeEach(resetDatabase);
describe("upload session bitmap", () => {
it("matches the bit order of PostgreSQL's set_bit", async () => {
const { userId } = await createUser();
const id = crypto.randomUUID();
await UploadRepo.createFileUploadSession({
...newEntryParams(userId, "root"),
id,
path: "unused",
totalChunks: 20,
expiresAt: new Date(Date.now() + 60_000),
hskVersion: null,
contentType: "image/png",
encCreatedAt: null,
encLastModifiedAt: ciphertext(),
});
const uploaded = [1, 8, 9, 20];
for (const chunkIndex of uploaded) {
await UploadRepo.markChunkAsUploaded(id, chunkIndex);
}
const session = await UploadRepo.getUploadSession(id, userId);
expect(session?.uploadedChunks).toBe(uploaded.length);
for (let chunkIndex = 1; chunkIndex <= 20; chunkIndex++) {
expect(isChunkUploaded(session!.bitmap, chunkIndex)).toBe(uploaded.includes(chunkIndex));
}
});
});