mana-swift-llm/Tests/ManaLLMTests/ByokImagePrepTests.swift
till 3ceca017f1 Nativer EXIF-Strip für Vision: ByokImagePrep + describeImage(rawImageData:)
Schließt die compliance-kritische native Lücke (web hatte
prepareImageForVision, nativ nicht).
- ByokImagePrep.prepareForVision: ImageIO-Re-Encode → EXIF/GPS gestrippt +
  Downscale ≤2048px (Orientierung in Pixel gebacken, kein Metadaten-Dict).
- ManaLLM.describeImage(rawImageData:…): Convenience, die den Strip selbst
  macht → Native-Konsument kann ihn nicht vergessen.
- 3 Tests (Downscale-Cap verifiziert, Garbage→nil), swift test 39/39 grün.

Native Vision/TTS-KONSUMENT bleibt Xcode (Tills Hand) — das hier ist das
verifizierbare Fundament dafür.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 16:18:08 +02:00

71 lines
2.8 KiB
Swift

import CoreGraphics
import Foundation
import ImageIO
@testable import ManaLLM
import Testing
import UniformTypeIdentifiers
/// Tests für die Vision-Bild-Vorbereitung (EXIF-Strip + Downscale).
/// Erzeugt In-Memory-JPEGs — netzwerk- und fixture-frei.
struct ByokImagePrepTests {
/// Einfarbiges JPEG der gewünschten Größe.
private func makeJpeg(width: Int, height: Int) -> Data {
let colorSpace = CGColorSpaceCreateDeviceRGB()
let context = CGContext(
data: nil,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: 0,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue
)!
context.setFillColor(CGColor(red: 0.2, green: 0.6, blue: 0.3, alpha: 1))
context.fill(CGRect(x: 0, y: 0, width: width, height: height))
let image = context.makeImage()!
let output = NSMutableData()
let dest = CGImageDestinationCreateWithData(
output as CFMutableData, UTType.jpeg.identifier as CFString, 1, nil
)!
CGImageDestinationAddImage(dest, image, nil)
_ = CGImageDestinationFinalize(dest)
return output as Data
}
private func pixelSize(of data: Data) -> (width: Int, height: Int)? {
guard let src = CGImageSourceCreateWithData(data as CFData, nil),
let props = CGImageSourceCopyPropertiesAtIndex(src, 0, nil) as? [CFString: Any],
let w = props[kCGImagePropertyPixelWidth] as? Int,
let h = props[kCGImagePropertyPixelHeight] as? Int
else { return nil }
return (w, h)
}
@Test func downscalesLargeImageAndReturnsJpeg() throws {
let big = makeJpeg(width: 4000, height: 3000)
let prepared = ByokImagePrep.prepareForVision(big, maxEdge: 1024)
#expect(prepared != nil)
#expect(prepared?.mimeType == "image/jpeg")
let size = try pixelSize(of: #require(prepared?.data))
#expect(size != nil)
#expect(try max(#require(size?.width), #require(size?.height)) <= 1024)
#expect(try max(#require(size?.width), #require(size?.height)) > 0)
}
@Test func keepsSmallImageAndStripsToJpeg() throws {
let small = makeJpeg(width: 120, height: 90)
let prepared = ByokImagePrep.prepareForVision(small, maxEdge: 2048)
#expect(prepared != nil)
#expect(prepared?.mimeType == "image/jpeg")
// Kleiner als maxEdge → nicht hochskaliert.
let size = try pixelSize(of: #require(prepared?.data))
#expect(size != nil)
#expect(try max(#require(size?.width), #require(size?.height)) <= 120)
}
@Test func returnsNilForGarbageData() {
let prepared = ByokImagePrep.prepareForVision(Data([0x00, 0x01, 0x02, 0x03]))
#expect(prepared == nil)
}
}