8c94e9005f
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Co-authored-by: Kit Langton <kit.langton@gmail.com> Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Affan Ali <93028901+affanali2k3@users.noreply.github.com> Co-authored-by: affanali2k3 <affanalikhanxx@gmail.com> Co-authored-by: Frank <frank@anoma.ly> Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com> Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local> Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: Jay V <air@live.ca> Co-authored-by: Dax Raad <d@ironbay.co> Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com> Co-authored-by: OpeOginni <107570612+OpeOginni@users.noreply.github.com> Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> Co-authored-by: Ben Guthrie <benjee.012@gmail.com> Co-authored-by: Dax <mail@thdxr.com> Co-authored-by: Filip <34747899+neriousy@users.noreply.github.com> Co-authored-by: Max Anderson <max.a.anderson95@gmail.com> Co-authored-by: Brendan Allan <git@brendonovich.dev> Co-authored-by: Jack <jack@anoma.ly> Co-authored-by: Shoubhit Dash <shoubhit2005@gmail.com> Co-authored-by: Dustin Deus <deusdustin@gmail.com> Co-authored-by: starptech <starptech@starptechs-MBP.fritz.box> Co-authored-by: Aiden Cline <aidenpcline@gmail.com> Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com> Co-authored-by: Jay <53023+jayair@users.noreply.github.com> Co-authored-by: runvip <164729189+runvip@users.noreply.github.com> Co-authored-by: opencode <opencode@sst.dev> Co-authored-by: Julian Coy <julian@ex-machina.co> Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com>
122 lines
4.9 KiB
TypeScript
122 lines
4.9 KiB
TypeScript
import { describe, expect } from "bun:test"
|
|
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
|
import { Cause, Effect, Exit } from "effect"
|
|
import { Image } from "@/image/image"
|
|
import { Config } from "@/config/config"
|
|
import { MessageID, PartID, SessionID } from "@/session/schema"
|
|
import path from "node:path"
|
|
import { TestConfig } from "../fixture/config"
|
|
import { testEffect } from "../lib/effect"
|
|
|
|
const it = testEffect(LayerNode.compile(Image.node, [[Config.node, TestConfig.layer()]]))
|
|
const tiny = testEffect(
|
|
LayerNode.compile(Image.node, [
|
|
[Config.node, TestConfig.layer({ get: () => Effect.succeed({ attachment: { image: { max_base64_bytes: 1 } } }) })],
|
|
]),
|
|
)
|
|
|
|
function part(mime: string, data: string) {
|
|
return {
|
|
id: PartID.ascending(),
|
|
messageID: MessageID.ascending(),
|
|
sessionID: SessionID.make("ses_test"),
|
|
type: "file" as const,
|
|
mime,
|
|
url: `data:${mime};base64,${data}`,
|
|
}
|
|
}
|
|
|
|
describe("Image", () => {
|
|
it.effect("normalizes generated png and jpeg attachments", () =>
|
|
Effect.gen(function* () {
|
|
const photon = yield* Effect.promise(() => import("@silvia-odwyer/photon-node"))
|
|
const source = new photon.PhotonImage(
|
|
new Uint8Array(Array.from({ length: 64 * 64 * 4 }, (_, index) => (index % 4 === 3 ? 255 : index % 251))),
|
|
64,
|
|
64,
|
|
)
|
|
const image = yield* Image.Service
|
|
const results = yield* Effect.all([
|
|
image.normalize(part("image/png", Buffer.from(source.get_bytes()).toString("base64"))),
|
|
image.normalize(part("image/jpeg", Buffer.from(source.get_bytes_jpeg(90)).toString("base64"))),
|
|
])
|
|
|
|
source.free()
|
|
expect(results.map((result) => result.url.startsWith(`data:${result.mime};base64,`))).toEqual([true, true])
|
|
expect(results.every((result) => result.mime === "image/png" || result.mime === "image/jpeg")).toBe(true)
|
|
}),
|
|
)
|
|
|
|
it.effect("accepts webp attachments that are already within limits", () =>
|
|
Effect.gen(function* () {
|
|
const image = yield* Image.Service
|
|
const input = part("image/webp", "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA")
|
|
|
|
expect(yield* image.normalize(input)).toEqual(input)
|
|
}),
|
|
)
|
|
|
|
it.effect("resizes images that fit the byte limit but exceed dimension limits", () =>
|
|
Effect.gen(function* () {
|
|
const photon = yield* Effect.promise(() => import("@silvia-odwyer/photon-node"))
|
|
const source = new photon.PhotonImage(new Uint8Array(Array.from({ length: 9_000 * 4 }, () => 255)), 9_000, 1)
|
|
const image = yield* Image.Service
|
|
const result = yield* image.normalize(part("image/png", Buffer.from(source.get_bytes()).toString("base64")))
|
|
const resized = photon.PhotonImage.new_from_byteslice(
|
|
Buffer.from(result.url.slice(result.url.indexOf(";base64,") + ";base64,".length), "base64"),
|
|
)
|
|
|
|
source.free()
|
|
expect(resized.get_width()).toBeLessThanOrEqual(2_000)
|
|
expect(resized.get_height()).toBeLessThanOrEqual(2_000)
|
|
resized.free()
|
|
}),
|
|
)
|
|
|
|
it.effect("resizes the 5MB base64 picture fixture", () =>
|
|
Effect.gen(function* () {
|
|
const photon = yield* Effect.promise(() => import("@silvia-odwyer/photon-node"))
|
|
const data = Buffer.from(
|
|
yield* Effect.promise(() =>
|
|
Bun.file(path.join(import.meta.dir, "fixtures", "picture-5mb-base64.png")).arrayBuffer(),
|
|
),
|
|
)
|
|
const input = part("image/png", data.toString("base64"))
|
|
const image = yield* Image.Service
|
|
const result = yield* image.normalize(input)
|
|
const base64 = result.url.slice(result.url.indexOf(";base64,") + ";base64,".length)
|
|
const resized = photon.PhotonImage.new_from_byteslice(Buffer.from(base64, "base64"))
|
|
|
|
expect(input.url.slice(input.url.indexOf(";base64,") + ";base64,".length).length).toBe(5 * 1024 * 1024)
|
|
expect(result.url).not.toBe(input.url)
|
|
expect(base64.length).toBeLessThan(5 * 1024 * 1024)
|
|
expect(resized.get_width()).toBeLessThanOrEqual(2_000)
|
|
expect(resized.get_height()).toBeLessThanOrEqual(2_000)
|
|
resized.free()
|
|
}),
|
|
)
|
|
|
|
tiny.effect("fails with a typed size error when no resized candidate fits", () =>
|
|
Effect.gen(function* () {
|
|
const photon = yield* Effect.promise(() => import("@silvia-odwyer/photon-node"))
|
|
const source = new photon.PhotonImage(new Uint8Array(Array.from({ length: 4 }, () => 255)), 1, 1)
|
|
const image = yield* Image.Service
|
|
const exit = yield* image
|
|
.normalize(part("image/png", Buffer.from(source.get_bytes()).toString("base64")))
|
|
.pipe(Effect.exit)
|
|
|
|
source.free()
|
|
expect(Exit.isFailure(exit)).toBe(true)
|
|
if (Exit.isFailure(exit)) {
|
|
const error = Cause.squash(exit.cause)
|
|
expect(error).toBeInstanceOf(Image.SizeError)
|
|
if (error instanceof Image.SizeError) {
|
|
expect(error.width).toBe(1)
|
|
expect(error.height).toBe(1)
|
|
expect(error.max).toBe(1)
|
|
}
|
|
}
|
|
}),
|
|
)
|
|
})
|