feat(media-supply): add media resource supply marketplace
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
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>
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
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())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user