87874e64cf
Send the screenshot to the multimodal model inline as a base64 data URL so summarisation no longer requires S3 to be configured or publicly reachable. Only when the PNG exceeds the provider MaxInlineBytes limit do we upload to S3, verify the public URL is reachable, and delete the temporary object once done. Oversized images without S3 now surface a clear hint instead of a hard S3 requirement.
141 lines
3.8 KiB
Go
141 lines
3.8 KiB
Go
package application
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type fakeOSSProvider struct {
|
|
key string
|
|
data []byte
|
|
contentType string
|
|
url string
|
|
deletedKey string
|
|
}
|
|
|
|
func (f *fakeOSSProvider) Upload(_ context.Context, key string, data []byte, contentType string) (string, error) {
|
|
f.key = key
|
|
f.data = append([]byte(nil), data...)
|
|
f.contentType = contentType
|
|
if f.url == "" {
|
|
return "https://cdn.example.com/" + key, nil
|
|
}
|
|
return f.url, nil
|
|
}
|
|
|
|
func (f *fakeOSSProvider) Name() string { return "fake" }
|
|
|
|
func (f *fakeOSSProvider) Delete(_ context.Context, key string) error {
|
|
f.deletedKey = key
|
|
return nil
|
|
}
|
|
|
|
type fakeSummarizer struct {
|
|
prompt string
|
|
url string
|
|
text string
|
|
}
|
|
|
|
func (f *fakeSummarizer) SummarizeImage(_ context.Context, prompt string, imageURL string) (string, error) {
|
|
f.prompt = prompt
|
|
f.url = imageURL
|
|
if f.text == "" {
|
|
return "summary text", nil
|
|
}
|
|
return f.text, nil
|
|
}
|
|
|
|
func TestCaptureSummaryServiceUploadsSummarizesAndCopies(t *testing.T) {
|
|
ctx := context.Background()
|
|
provider := &fakeOSSProvider{}
|
|
summarizer := &fakeSummarizer{}
|
|
clip := &fakeClipboard{}
|
|
svc := &CaptureSummaryService{
|
|
Provider: provider,
|
|
Summarizer: summarizer,
|
|
Clipboard: clip,
|
|
Prompt: "describe screenshot",
|
|
PathPrefix: "snapgo/",
|
|
}
|
|
|
|
uploaded, err := svc.UploadImage(ctx, []byte("png"))
|
|
if err != nil {
|
|
t.Fatalf("upload image: %v", err)
|
|
}
|
|
if provider.contentType != "image/png" {
|
|
t.Fatalf("expected image/png content type, got %q", provider.contentType)
|
|
}
|
|
if string(provider.data) != "png" {
|
|
t.Fatalf("expected uploaded png bytes, got %q", string(provider.data))
|
|
}
|
|
|
|
summary, err := svc.Summarize(ctx, uploaded.URL)
|
|
if err != nil {
|
|
t.Fatalf("summarize: %v", err)
|
|
}
|
|
if summarizer.prompt != "describe screenshot" {
|
|
t.Fatalf("expected prompt forwarded, got %q", summarizer.prompt)
|
|
}
|
|
if summarizer.url != uploaded.URL {
|
|
t.Fatalf("expected uploaded url forwarded, got %q", summarizer.url)
|
|
}
|
|
if summary != "summary text" {
|
|
t.Fatalf("expected summary text, got %q", summary)
|
|
}
|
|
|
|
if err := svc.CopySummary(ctx, summary); err != nil {
|
|
t.Fatalf("copy summary: %v", err)
|
|
}
|
|
if clip.text != "summary text" {
|
|
t.Fatalf("expected summary copied, got %q", clip.text)
|
|
}
|
|
}
|
|
|
|
func TestCaptureSummaryServiceInlineDataURL(t *testing.T) {
|
|
svc := &CaptureSummaryService{MaxInlineBytes: 10}
|
|
|
|
if !svc.CanInline([]byte("small")) {
|
|
t.Fatalf("expected 5-byte payload to be inlinable under 10-byte cap")
|
|
}
|
|
if svc.CanInline([]byte("this is definitely too big")) {
|
|
t.Fatalf("expected oversized payload to be rejected for inlining")
|
|
}
|
|
|
|
url := svc.InlineDataURL([]byte("png"))
|
|
if !strings.HasPrefix(url, "data:image/png;base64,") {
|
|
t.Fatalf("expected base64 data URL prefix, got %q", url)
|
|
}
|
|
if strings.Contains(url, "https://") {
|
|
t.Fatalf("inline URL must not be a remote URL, got %q", url)
|
|
}
|
|
}
|
|
|
|
func TestCaptureSummaryServiceDeleteUploadedUsesDeleter(t *testing.T) {
|
|
provider := &fakeOSSProvider{}
|
|
svc := &CaptureSummaryService{Provider: provider}
|
|
|
|
if err := svc.DeleteUploaded(context.Background(), "snapgo/a.png"); err != nil {
|
|
t.Fatalf("delete uploaded: %v", err)
|
|
}
|
|
if provider.deletedKey != "snapgo/a.png" {
|
|
t.Fatalf("expected object deleted, got %q", provider.deletedKey)
|
|
}
|
|
}
|
|
|
|
func TestCaptureSummaryServiceDeleteUploadedNoDeleterIsNoop(t *testing.T) {
|
|
// A provider without Delete must not error when cleanup is requested.
|
|
svc := &CaptureSummaryService{Provider: nonDeletingProvider{}}
|
|
if err := svc.DeleteUploaded(context.Background(), "snapgo/a.png"); err != nil {
|
|
t.Fatalf("expected no-op delete, got %v", err)
|
|
}
|
|
}
|
|
|
|
type nonDeletingProvider struct{}
|
|
|
|
func (nonDeletingProvider) Upload(_ context.Context, _ string, _ []byte, _ string) (string, error) {
|
|
return "https://cdn.example.com/x.png", nil
|
|
}
|
|
|
|
func (nonDeletingProvider) Name() string { return "no-delete" }
|