9d6181260a
Desktop Client Build / Resolve Build Metadata (push) Successful in 43s
Frontend CI / Frontend (push) Successful in 3m49s
Backend CI / Backend (push) Failing after 7m10s
Desktop Client Build / Build Desktop Client (push) Successful in 23m4s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 28s
Introduce an end-to-end media-supply feature: tenant-side resource sync service/worker backed by a Meijiequan supplier client, ops-side management APIs, and admin/ops web views for resources, orders, favorites and submission. Adds a shared digitocr helper, MediaSupply config blocks for tenant and ops, shared types, and migrations for supplier media resources, price overrides, customer visibility and order refunds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
67 lines
1.9 KiB
Go
67 lines
1.9 KiB
Go
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"image"
|
|
"image/color"
|
|
"image/png"
|
|
"testing"
|
|
|
|
"github.com/geo-platform/tenant-api/internal/shared/config"
|
|
)
|
|
|
|
func newTestChallengePNG(t *testing.T) []byte {
|
|
t.Helper()
|
|
img := image.NewRGBA(image.Rect(0, 0, 50, 25))
|
|
orange := color.RGBA{R: 240, G: 130, B: 40, A: 255}
|
|
// Four orange blocks emulate the four-digit captcha; digitocr only needs to
|
|
// segment and match them, so exact glyphs are not required for this test.
|
|
for d := 0; d < 4; d++ {
|
|
x0 := 5 + d*10
|
|
for y := 5; y < 21; y++ {
|
|
for x := x0; x < x0+8; x++ {
|
|
img.Set(x, y, orange)
|
|
}
|
|
}
|
|
}
|
|
var buf bytes.Buffer
|
|
if err := png.Encode(&buf, img); err != nil {
|
|
t.Fatalf("encode png: %v", err)
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func TestDigitOCRChallengeResolverDecodesImage(t *testing.T) {
|
|
resolver := DigitOCRMeijiequanChallengeCodeResolver{}
|
|
code, err := resolver.ResolveCode(context.Background(), MeijiequanChallenge{
|
|
Image: newTestChallengePNG(t),
|
|
ContentType: "image/png",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("resolve code: %v", err)
|
|
}
|
|
if len(code) != meijiequanChallengeDigits {
|
|
t.Fatalf("expected %d digit code, got %q", meijiequanChallengeDigits, code)
|
|
}
|
|
for _, r := range code {
|
|
if r < '0' || r > '9' {
|
|
t.Fatalf("expected numeric code, got %q", code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDigitOCRChallengeResolverRejectsEmptyImage(t *testing.T) {
|
|
resolver := DigitOCRMeijiequanChallengeCodeResolver{}
|
|
if _, err := resolver.ResolveCode(context.Background(), MeijiequanChallenge{}); err == nil {
|
|
t.Fatal("expected error for empty challenge image")
|
|
}
|
|
}
|
|
|
|
func TestNewMeijiequanClientDefaultsToDigitOCRResolver(t *testing.T) {
|
|
client := NewMeijiequanClient(nil, config.MeijiequanConfig{})
|
|
if _, ok := client.challengeResolver().(DigitOCRMeijiequanChallengeCodeResolver); !ok {
|
|
t.Fatalf("expected digitocr resolver by default, got %T", client.challengeResolver())
|
|
}
|
|
}
|