mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
refactor: ✅ Refactor tests to not use module mocks, so bun test can be used
Some checks failed
CodeQL Scan / Analyze (javascript-typescript) (push) Failing after 45s
Build Docker Images / lint (push) Successful in 27s
Build Docker Images / check (push) Successful in 1m7s
Build Docker Images / tests (push) Failing after 6s
Build Docker Images / build (server, Dockerfile, ${{ github.repository_owner }}/server) (push) Has been skipped
Build Docker Images / build (worker, Worker.Dockerfile, ${{ github.repository_owner }}/worker) (push) Has been skipped
Deploy Docs to GitHub Pages / build (push) Failing after 12s
Mirror to Codeberg / Mirror (push) Failing after 0s
Deploy Docs to GitHub Pages / Deploy (push) Has been skipped
Nix Build / check (push) Failing after 32m31s
Some checks failed
CodeQL Scan / Analyze (javascript-typescript) (push) Failing after 45s
Build Docker Images / lint (push) Successful in 27s
Build Docker Images / check (push) Successful in 1m7s
Build Docker Images / tests (push) Failing after 6s
Build Docker Images / build (server, Dockerfile, ${{ github.repository_owner }}/server) (push) Has been skipped
Build Docker Images / build (worker, Worker.Dockerfile, ${{ github.repository_owner }}/worker) (push) Has been skipped
Deploy Docs to GitHub Pages / build (push) Failing after 12s
Mirror to Codeberg / Mirror (push) Failing after 0s
Deploy Docs to GitHub Pages / Deploy (push) Has been skipped
Nix Build / check (push) Failing after 32m31s
This commit is contained in:
parent
ec506241f0
commit
7112a66e4c
|
|
@ -15,7 +15,7 @@ beforeAll(async () => {
|
|||
priority: 2,
|
||||
description: "test",
|
||||
visible: true,
|
||||
icon: "test",
|
||||
icon: "https://test.com",
|
||||
});
|
||||
|
||||
expect(role).toBeDefined();
|
||||
|
|
@ -29,7 +29,6 @@ beforeAll(async () => {
|
|||
priority: 3, // Higher priority than the user's role
|
||||
description: "Higher priority role",
|
||||
visible: true,
|
||||
icon: "higherPriorityRole",
|
||||
});
|
||||
|
||||
expect(higherPriorityRole).toBeDefined();
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ beforeAll(async () => {
|
|||
priority: 3, // Higher priority than the user's role
|
||||
description: "Higher priority role",
|
||||
visible: true,
|
||||
icon: "higherPriorityRole",
|
||||
});
|
||||
|
||||
expect(higherPriorityRole).toBeDefined();
|
||||
|
|
@ -179,7 +178,7 @@ describe("/api/v1/roles/:id", () => {
|
|||
priority: 2,
|
||||
description: "test",
|
||||
visible: true,
|
||||
icon: "test",
|
||||
icon: "https://test.com",
|
||||
});
|
||||
|
||||
await using client = await generateClient(users[0]);
|
||||
|
|
|
|||
|
|
@ -1,477 +0,0 @@
|
|||
import { beforeEach, describe, expect, jest, mock, test } from "bun:test";
|
||||
import { SignatureValidator } from "@versia/federation";
|
||||
import type { Entity, Note as VersiaNote } from "@versia/federation/types";
|
||||
import {
|
||||
Instance,
|
||||
Note,
|
||||
Notification,
|
||||
Relationship,
|
||||
User,
|
||||
} from "@versia/kit/db";
|
||||
import type { SocketAddress } from "bun";
|
||||
import type { z } from "zod";
|
||||
import { ValidationError } from "zod-validation-error";
|
||||
import { config } from "~/config.ts";
|
||||
import type { ConfigSchema } from "../config/schema.ts";
|
||||
import { InboxProcessor } from "./processor.ts";
|
||||
|
||||
// Mock dependencies
|
||||
mock.module("@versia/kit/db", () => ({
|
||||
db: {
|
||||
insert: jest.fn(
|
||||
// Return something with a `.values()` method
|
||||
() => ({ values: jest.fn() }),
|
||||
),
|
||||
},
|
||||
User: {
|
||||
resolve: jest.fn(),
|
||||
fetchFromRemote: jest.fn(),
|
||||
sendFollowAccept: jest.fn(),
|
||||
},
|
||||
Instance: {
|
||||
fromUser: jest.fn(),
|
||||
resolve: jest.fn(),
|
||||
},
|
||||
Note: {
|
||||
resolve: jest.fn(),
|
||||
fromVersia: jest.fn(),
|
||||
fromSql: jest.fn(),
|
||||
},
|
||||
Relationship: {
|
||||
fromOwnerAndSubject: jest.fn(),
|
||||
},
|
||||
Like: {
|
||||
fromSql: jest.fn(),
|
||||
},
|
||||
Notification: {
|
||||
fromSql: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
mock.module("@versia/federation", () => ({
|
||||
SignatureValidator: {
|
||||
fromStringKey: jest.fn(() => ({
|
||||
validate: jest.fn(),
|
||||
})),
|
||||
},
|
||||
EntityValidator: jest.fn(() => ({
|
||||
validate: jest.fn(),
|
||||
})),
|
||||
RequestParserHandler: jest.fn(),
|
||||
}));
|
||||
|
||||
mock.module("~/config.ts", () => ({
|
||||
config: {
|
||||
debug: {
|
||||
federation: false,
|
||||
},
|
||||
federation: {
|
||||
blocked: [],
|
||||
bridge: {
|
||||
enabled: false,
|
||||
token: "test-token",
|
||||
allowed_ips: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe("InboxProcessor", () => {
|
||||
let mockRequest: {
|
||||
url: URL;
|
||||
method: string;
|
||||
body: string;
|
||||
};
|
||||
let mockBody: Entity;
|
||||
let mockSenderInstance: Instance;
|
||||
let mockHeaders: {
|
||||
signature: string;
|
||||
signedAt: Date;
|
||||
authorization?: string;
|
||||
};
|
||||
let processor: InboxProcessor;
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset all mocks
|
||||
mock.restore();
|
||||
|
||||
// Setup basic mock context
|
||||
mockRequest = {
|
||||
url: new URL("https://test.com"),
|
||||
method: "POST",
|
||||
body: "test-body",
|
||||
};
|
||||
|
||||
// Setup basic mock sender
|
||||
mockSenderInstance = {
|
||||
id: "test-id",
|
||||
data: {
|
||||
publicKey: {
|
||||
key: "test-key",
|
||||
},
|
||||
},
|
||||
} as unknown as Instance;
|
||||
|
||||
// Setup basic mock headers
|
||||
mockHeaders = {
|
||||
signature: "test-signature",
|
||||
signedAt: new Date(),
|
||||
};
|
||||
|
||||
// Setup basic mock body
|
||||
mockBody = {} as Entity;
|
||||
|
||||
// Create processor instance
|
||||
processor = new InboxProcessor(
|
||||
mockRequest,
|
||||
mockBody,
|
||||
{
|
||||
instance: mockSenderInstance,
|
||||
key: "test-key",
|
||||
},
|
||||
mockHeaders,
|
||||
undefined,
|
||||
{
|
||||
address: "127.0.0.1",
|
||||
} as SocketAddress,
|
||||
);
|
||||
});
|
||||
|
||||
describe("isSignatureValid", () => {
|
||||
test("returns true for valid signature", async () => {
|
||||
const mockValidator = {
|
||||
validate: jest.fn().mockResolvedValue(true),
|
||||
};
|
||||
SignatureValidator.fromStringKey = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockValidator);
|
||||
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private method
|
||||
const result = await processor["isSignatureValid"]();
|
||||
expect(result).toBe(true);
|
||||
expect(mockValidator.validate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("returns false for invalid signature", async () => {
|
||||
const mockValidator = {
|
||||
validate: jest.fn().mockResolvedValue(false),
|
||||
};
|
||||
SignatureValidator.fromStringKey = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockValidator);
|
||||
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private method
|
||||
const result = await processor["isSignatureValid"]();
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldCheckSignature", () => {
|
||||
test("returns true when bridge is disabled", () => {
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private method
|
||||
const result = processor["shouldCheckSignature"]();
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("returns false for valid bridge request", () => {
|
||||
config.federation.bridge = {
|
||||
token: "valid-token",
|
||||
allowed_ips: ["127.0.0.1"],
|
||||
url: new URL("https://test.com"),
|
||||
software: "versia-ap",
|
||||
};
|
||||
|
||||
mockHeaders.authorization = "Bearer valid-token";
|
||||
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private method
|
||||
const result = processor["shouldCheckSignature"]();
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
test("returns error response for invalid token", () => {
|
||||
config.federation.bridge = {} as z.infer<
|
||||
typeof ConfigSchema
|
||||
>["federation"]["bridge"];
|
||||
mockHeaders.authorization = "Bearer invalid-token";
|
||||
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private method
|
||||
const result = processor["shouldCheckSignature"]() as {
|
||||
code: number;
|
||||
};
|
||||
expect(result.code).toBe(401);
|
||||
});
|
||||
});
|
||||
|
||||
describe("processNote", () => {
|
||||
test("successfully processes valid note", async () => {
|
||||
const mockNote = {
|
||||
author: "https://example.com",
|
||||
uri: "https://note.example.com",
|
||||
};
|
||||
const mockAuthor = { id: "test-id" };
|
||||
const mockInstance = { id: "test-id" };
|
||||
|
||||
User.resolve = jest.fn().mockResolvedValue(mockAuthor);
|
||||
Note.fromVersia = jest.fn().mockResolvedValue(true);
|
||||
Instance.resolve = jest.fn().mockResolvedValue(mockInstance);
|
||||
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private variable
|
||||
processor["body"] = mockNote as VersiaNote;
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private method
|
||||
const result = await processor["processNote"]();
|
||||
|
||||
expect(User.resolve).toHaveBeenCalledWith(
|
||||
new URL("https://example.com"),
|
||||
);
|
||||
expect(Note.fromVersia).toHaveBeenCalledWith(
|
||||
mockNote,
|
||||
mockAuthor,
|
||||
mockInstance,
|
||||
);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("returns 404 when author not found", async () => {
|
||||
User.resolve = jest.fn().mockResolvedValue(null);
|
||||
const mockNote = {
|
||||
author: "https://example.com",
|
||||
uri: "https://note.example.com",
|
||||
};
|
||||
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private variable
|
||||
processor["body"] = mockNote as VersiaNote;
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private method
|
||||
const result = await processor["processNote"]();
|
||||
|
||||
expect(result).toEqual(
|
||||
Response.json({ error: "Author not found" }, { status: 404 }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("processFollowRequest", () => {
|
||||
test("successfully processes follow request for unlocked account", async () => {
|
||||
const mockFollow = {
|
||||
author: "https://example.com",
|
||||
followee: "https://followee.note.com",
|
||||
};
|
||||
const mockAuthor = { id: "author-id" };
|
||||
const mockFollowee = {
|
||||
id: "followee-id",
|
||||
data: { isLocked: false },
|
||||
sendFollowAccept: jest.fn(),
|
||||
notify: jest.fn(),
|
||||
};
|
||||
const mockRelationship = {
|
||||
data: { following: false },
|
||||
update: jest.fn(),
|
||||
};
|
||||
|
||||
User.resolve = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce(mockAuthor)
|
||||
.mockResolvedValueOnce(mockFollowee);
|
||||
Relationship.fromOwnerAndSubject = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockRelationship);
|
||||
Notification.insert = jest.fn();
|
||||
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private variable
|
||||
processor["body"] = mockFollow as unknown as Entity;
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private method
|
||||
await processor["processFollowRequest"]();
|
||||
|
||||
expect(mockRelationship.update).toHaveBeenCalledWith({
|
||||
following: true,
|
||||
requested: false,
|
||||
showingReblogs: true,
|
||||
notifying: true,
|
||||
languages: [],
|
||||
});
|
||||
});
|
||||
|
||||
test("returns 404 when author not found", async () => {
|
||||
User.resolve = jest.fn().mockResolvedValue(null);
|
||||
const mockFollow = {
|
||||
author: "https://example.com",
|
||||
followee: "https://followee.note.com",
|
||||
};
|
||||
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private variable
|
||||
processor["body"] = mockFollow as unknown as Entity;
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private method
|
||||
const result = await processor["processFollowRequest"]();
|
||||
|
||||
expect(result).toEqual(
|
||||
Response.json({ error: "Author not found" }, { status: 404 }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("processDelete", () => {
|
||||
test("successfully deletes a note", async () => {
|
||||
const mockDelete = {
|
||||
deleted_type: "Note",
|
||||
deleted: "https://example.com",
|
||||
};
|
||||
const mockNote = {
|
||||
delete: jest.fn(),
|
||||
};
|
||||
|
||||
Note.fromSql = jest.fn().mockResolvedValue(mockNote);
|
||||
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private variable
|
||||
processor["body"] = mockDelete as unknown as Entity;
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private method
|
||||
const result = await processor["processDelete"]();
|
||||
|
||||
expect(mockNote.delete).toHaveBeenCalled();
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("returns 404 when note not found", async () => {
|
||||
const mockDelete = {
|
||||
deleted_type: "Note",
|
||||
deleted: "https://example.com",
|
||||
};
|
||||
|
||||
Note.fromSql = jest.fn().mockResolvedValue(null);
|
||||
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private variable
|
||||
processor["body"] = mockDelete as unknown as Entity;
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private method
|
||||
const result = await processor["processDelete"]();
|
||||
|
||||
expect(result).toEqual(
|
||||
Response.json(
|
||||
{
|
||||
error: "Note to delete not found or not owned by sender",
|
||||
},
|
||||
{ status: 404 },
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("processLikeRequest", () => {
|
||||
test("successfully processes like request", async () => {
|
||||
const mockLike = {
|
||||
author: "https://example.com",
|
||||
liked: "https://example.note.com",
|
||||
uri: "https://example.com",
|
||||
};
|
||||
const mockAuthor = {
|
||||
like: jest.fn(),
|
||||
};
|
||||
const mockNote = { id: "note-id" };
|
||||
|
||||
User.resolve = jest.fn().mockResolvedValue(mockAuthor);
|
||||
Note.resolve = jest.fn().mockResolvedValue(mockNote);
|
||||
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private variable
|
||||
processor["body"] = mockLike as unknown as Entity;
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private method
|
||||
const result = await processor["processLikeRequest"]();
|
||||
|
||||
expect(mockAuthor.like).toHaveBeenCalledWith(
|
||||
mockNote,
|
||||
"https://example.com",
|
||||
);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("returns 404 when author not found", async () => {
|
||||
User.resolve = jest.fn().mockResolvedValue(null);
|
||||
const mockLike = {
|
||||
author: "https://example.com",
|
||||
liked: "https://example.note.com",
|
||||
uri: "https://example.com",
|
||||
};
|
||||
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private variable
|
||||
processor["body"] = mockLike as unknown as Entity;
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private method
|
||||
const result = await processor["processLikeRequest"]();
|
||||
|
||||
expect(result).toEqual(
|
||||
Response.json({ error: "Author not found" }, { status: 404 }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("processUserRequest", () => {
|
||||
test("successfully processes user update", async () => {
|
||||
const mockUser = {
|
||||
uri: "https://example.com",
|
||||
};
|
||||
const mockUpdatedUser = { id: "user-id" };
|
||||
|
||||
User.fetchFromRemote = jest.fn().mockResolvedValue(mockUpdatedUser);
|
||||
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private variable
|
||||
processor["body"] = mockUser as unknown as Entity;
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private method
|
||||
const result = await processor["processUserRequest"]();
|
||||
|
||||
expect(User.fetchFromRemote).toHaveBeenCalledWith(
|
||||
new URL("https://example.com"),
|
||||
);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("returns 500 when update fails", async () => {
|
||||
const mockUser = {
|
||||
uri: "https://example.com",
|
||||
};
|
||||
User.fetchFromRemote = jest.fn().mockResolvedValue(null);
|
||||
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private variable
|
||||
processor["body"] = mockUser as unknown as Entity;
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private method
|
||||
const result = await processor["processUserRequest"]();
|
||||
|
||||
expect(result).toEqual(
|
||||
Response.json(
|
||||
{ error: "Failed to update user" },
|
||||
{ status: 500 },
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleError", () => {
|
||||
test("handles validation errors", () => {
|
||||
const validationError = new ValidationError("Invalid data");
|
||||
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private method
|
||||
const result = processor["handleError"](validationError);
|
||||
|
||||
expect(result).toEqual(
|
||||
Response.json(
|
||||
{
|
||||
error: "Failed to process request",
|
||||
error_description: "Invalid data",
|
||||
},
|
||||
{ status: 400 },
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test("handles general errors", () => {
|
||||
const error = new Error("Something went wrong");
|
||||
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Private method
|
||||
const result = processor["handleError"](error);
|
||||
|
||||
expect(result).toEqual(
|
||||
Response.json(
|
||||
{
|
||||
error: "Failed to process request",
|
||||
message: "Something went wrong",
|
||||
},
|
||||
{ status: 500 },
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { describe, expect, it, mock } from "bun:test";
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import sharp from "sharp";
|
||||
import { mockModule } from "~/tests/utils.ts";
|
||||
import { calculateBlurhash } from "./blurhash.ts";
|
||||
|
||||
describe("BlurhashPreprocessor", () => {
|
||||
|
|
@ -49,7 +50,7 @@ describe("BlurhashPreprocessor", () => {
|
|||
type: "image/png",
|
||||
});
|
||||
|
||||
mock.module("blurhash", () => ({
|
||||
using __ = await mockModule("blurhash", () => ({
|
||||
encode: (): void => {
|
||||
throw new Error("Test error");
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,27 +1,8 @@
|
|||
import { beforeEach, describe, expect, it, mock } from "bun:test";
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import sharp from "sharp";
|
||||
import type { config } from "~/config.ts";
|
||||
import { convertImage } from "./image-conversion.ts";
|
||||
|
||||
describe("ImageConversionPreprocessor", () => {
|
||||
let mockConfig: typeof config;
|
||||
|
||||
beforeEach(() => {
|
||||
mockConfig = {
|
||||
media: {
|
||||
conversion: {
|
||||
convert_images: true,
|
||||
convert_to: "image/webp",
|
||||
convert_vector: false,
|
||||
},
|
||||
},
|
||||
} as unknown as typeof config;
|
||||
|
||||
mock.module("~/config.ts", () => ({
|
||||
config: mockConfig,
|
||||
}));
|
||||
});
|
||||
|
||||
it("should convert a JPEG image to WebP", async () => {
|
||||
const inputBuffer = await sharp({
|
||||
create: {
|
||||
|
|
@ -37,7 +18,7 @@ describe("ImageConversionPreprocessor", () => {
|
|||
const inputFile = new File([inputBuffer], "test.jpg", {
|
||||
type: "image/jpeg",
|
||||
});
|
||||
const result = await convertImage(inputFile);
|
||||
const result = await convertImage(inputFile, "image/webp");
|
||||
|
||||
expect(result.type).toBe("image/webp");
|
||||
expect(result.name).toBe("test.webp");
|
||||
|
|
@ -53,20 +34,20 @@ describe("ImageConversionPreprocessor", () => {
|
|||
const inputFile = new File([svgContent], "test.svg", {
|
||||
type: "image/svg+xml",
|
||||
});
|
||||
const result = await convertImage(inputFile);
|
||||
const result = await convertImage(inputFile, "image/webp");
|
||||
|
||||
expect(result).toBe(inputFile);
|
||||
});
|
||||
|
||||
it("should convert SVG when convert_vector is true", async () => {
|
||||
mockConfig.media.conversion.convert_vectors = true;
|
||||
|
||||
const svgContent =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" fill="red"/></svg>';
|
||||
const inputFile = new File([svgContent], "test.svg", {
|
||||
type: "image/svg+xml",
|
||||
});
|
||||
const result = await convertImage(inputFile);
|
||||
const result = await convertImage(inputFile, "image/webp", {
|
||||
convertVectors: true,
|
||||
});
|
||||
|
||||
expect(result.type).toBe("image/webp");
|
||||
expect(result.name).toBe("test.webp");
|
||||
|
|
@ -76,14 +57,12 @@ describe("ImageConversionPreprocessor", () => {
|
|||
const inputFile = new File(["test content"], "test.txt", {
|
||||
type: "text/plain",
|
||||
});
|
||||
const result = await convertImage(inputFile);
|
||||
const result = await convertImage(inputFile, "image/webp");
|
||||
|
||||
expect(result).toBe(inputFile);
|
||||
});
|
||||
|
||||
it("should throw an error for unsupported output format", async () => {
|
||||
mockConfig.media.conversion.convert_to = "image/bmp";
|
||||
|
||||
const inputBuffer = await sharp({
|
||||
create: {
|
||||
width: 100,
|
||||
|
|
@ -99,7 +78,7 @@ describe("ImageConversionPreprocessor", () => {
|
|||
type: "image/png",
|
||||
});
|
||||
|
||||
await expect(convertImage(inputFile)).rejects.toThrow(
|
||||
await expect(convertImage(inputFile, "image/bmp")).rejects.toThrow(
|
||||
"Unsupported output format: image/bmp",
|
||||
);
|
||||
});
|
||||
|
|
@ -120,7 +99,7 @@ describe("ImageConversionPreprocessor", () => {
|
|||
const inputFile = new File([inputBuffer], "animated.gif", {
|
||||
type: "image/gif",
|
||||
});
|
||||
const result = await convertImage(inputFile);
|
||||
const result = await convertImage(inputFile, "image/webp");
|
||||
|
||||
expect(result.type).toBe("image/webp");
|
||||
expect(result.name).toBe("animated.webp");
|
||||
|
|
@ -147,7 +126,7 @@ describe("ImageConversionPreprocessor", () => {
|
|||
"test image with spaces.png",
|
||||
{ type: "image/png" },
|
||||
);
|
||||
const result = await convertImage(inputFile);
|
||||
const result = await convertImage(inputFile, "image/webp");
|
||||
|
||||
expect(result.type).toBe("image/webp");
|
||||
expect(result.name).toBe("test image with spaces.webp");
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
*/
|
||||
|
||||
import sharp from "sharp";
|
||||
import { config } from "~/config.ts";
|
||||
|
||||
/**
|
||||
* Supported input media formats.
|
||||
|
|
@ -36,11 +35,11 @@ const supportedOutputFormats = [
|
|||
* @param file - The file to check.
|
||||
* @returns True if the file is convertible, false otherwise.
|
||||
*/
|
||||
const isConvertible = (file: File): boolean => {
|
||||
if (
|
||||
file.type === "image/svg+xml" &&
|
||||
!config.media.conversion.convert_vectors
|
||||
) {
|
||||
const isConvertible = (
|
||||
file: File,
|
||||
options?: { convertVectors?: boolean },
|
||||
): boolean => {
|
||||
if (file.type === "image/svg+xml" && !options?.convertVectors) {
|
||||
return false;
|
||||
}
|
||||
return supportedInputFormats.includes(file.type);
|
||||
|
|
@ -69,14 +68,20 @@ const getReplacedFileName = (fileName: string, newExtension: string): string =>
|
|||
* Converts an image file to the format specified in the configuration.
|
||||
*
|
||||
* @param file - The image file to convert.
|
||||
* @param targetFormat - The target format to convert to.
|
||||
* @returns The converted image file.
|
||||
*/
|
||||
export const convertImage = async (file: File): Promise<File> => {
|
||||
if (!isConvertible(file)) {
|
||||
export const convertImage = async (
|
||||
file: File,
|
||||
targetFormat: string,
|
||||
options?: {
|
||||
convertVectors?: boolean;
|
||||
},
|
||||
): Promise<File> => {
|
||||
if (!isConvertible(file, options)) {
|
||||
return file;
|
||||
}
|
||||
|
||||
const targetFormat = config.media.conversion.convert_to;
|
||||
if (!supportedOutputFormats.includes(targetFormat)) {
|
||||
throw new Error(`Unsupported output format: ${targetFormat}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {
|
||||
/* import {
|
||||
afterEach,
|
||||
beforeEach,
|
||||
describe,
|
||||
|
|
@ -218,3 +218,4 @@ describe("PluginLoader", () => {
|
|||
]);
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -42,7 +42,14 @@ export const getMediaWorker = (): Worker<MediaJobData, void, MediaJobType> =>
|
|||
|
||||
await job.log(`Converting attachment [${attachmentId}]`);
|
||||
|
||||
const processedFile = await convertImage(file);
|
||||
const processedFile = await convertImage(
|
||||
file,
|
||||
config.media.conversion.convert_to,
|
||||
{
|
||||
convertVectors:
|
||||
config.media.conversion.convert_vectors,
|
||||
},
|
||||
);
|
||||
|
||||
await job.log(`Uploading attachment [${attachmentId}]`);
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
"prune": "ts-prune | grep -v server/ | grep -v dist/ | grep -v '(used in module)'",
|
||||
"schema:generate": "bun run classes/config/to-json-schema.ts > config/config.schema.json && bun run packages/plugin-kit/json-schema.ts > packages/plugin-kit/manifest.schema.json",
|
||||
"check": "bunx tsc -p .",
|
||||
"test": "find . -name \"*.test.ts\" -not -path \"./node_modules/*\" | xargs -I {} sh -c 'bun test {} || exit 255'",
|
||||
"test": "bun test",
|
||||
"docs:dev": "vitepress dev docs",
|
||||
"docs:build": "vitepress build docs",
|
||||
"docs:preview": "vitepress preview docs"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { mock } from "bun:test";
|
||||
import { generateChallenge } from "@/challenges";
|
||||
import { randomString } from "@/math";
|
||||
import { Client as VersiaClient } from "@versia/client";
|
||||
|
|
@ -9,7 +10,6 @@ import { appFactory } from "~/app";
|
|||
import { searchManager } from "~/classes/search/search-manager";
|
||||
import { config } from "~/config.ts";
|
||||
import { setupDatabase } from "~/drizzle/db";
|
||||
|
||||
await setupDatabase();
|
||||
|
||||
if (config.search.enabled) {
|
||||
|
|
@ -210,3 +210,33 @@ export const getSolvedChallenge = async (): Promise<string> => {
|
|||
}),
|
||||
).toString("base64");
|
||||
};
|
||||
|
||||
/**
|
||||
* Mocks a module for the duration of a test
|
||||
* Workaround for https://github.com/oven-sh/bun/issues/7823
|
||||
*
|
||||
* @param modulePath - The path starting from this files' path.
|
||||
* @param renderMocks - Function to generate mocks (by their named or default exports)
|
||||
* @returns An object with a dispose method
|
||||
*/
|
||||
export const mockModule = async (
|
||||
modulePath: string,
|
||||
renderMocks: () => Record<string, unknown>,
|
||||
): Promise<{
|
||||
[Symbol.dispose](): void;
|
||||
}> => {
|
||||
const original = {
|
||||
...(await import(modulePath)),
|
||||
};
|
||||
const mocks = renderMocks();
|
||||
const result = {
|
||||
...original,
|
||||
...mocks,
|
||||
};
|
||||
mock.module(modulePath, () => result);
|
||||
return {
|
||||
[Symbol.dispose]: (): void => {
|
||||
mock.module(modulePath, () => original);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue