104 lines
3.5 KiB
Go
104 lines
3.5 KiB
Go
package webhook
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/jobs"
|
|
)
|
|
|
|
func TestBodyAndSignatureMatchSharedFixture(t *testing.T) {
|
|
fixture := loadWebhookFixture(t)
|
|
updatedAt, err := time.Parse(time.RFC3339Nano, fixture.Job.UpdatedAt)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
job := jobs.Job{ID: fixture.Job.ID, Status: jobs.Status(fixture.Job.Status), Capability: fixture.Job.Capability, OutputAssetIDs: fixture.Job.OutputAssetIDs, Error: fixture.Job.Error, UpdatedAt: updatedAt}
|
|
body, err := Body(job)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(body) != fixture.Body {
|
|
t.Fatalf("body = %s, want %s", body, fixture.Body)
|
|
}
|
|
if got := Sign(body, fixture.Secret); got != fixture.Signature {
|
|
t.Fatalf("signature = %q, want %q", got, fixture.Signature)
|
|
}
|
|
}
|
|
|
|
func TestDeliverRetriesUntilSuccessAndPreservesExactRequest(t *testing.T) {
|
|
clock := time.Date(2026, 8, 13, 8, 0, 0, 0, time.UTC)
|
|
sender := &senderStub{responses: []sendResult{{response: Response{Status: 503}}, {err: errors.New("network")}, {response: Response{Status: 204}}}}
|
|
deliverer := NewDeliverer(sender, " secret ", func() time.Time { return clock })
|
|
job := jobs.Job{ID: "job", Status: jobs.StatusSucceeded, Capability: "image.generate", OutputAssetIDs: []string{}, UpdatedAt: clock, WebhookURL: "https://hooks.example.test/job"}
|
|
|
|
result, err := deliverer.Deliver(context.Background(), job)
|
|
if err != nil || result.Attempts != 3 || result.LastStatus == nil || !result.LastStatus.OK || result.LastStatus.Status != 204 {
|
|
t.Fatalf("Deliver = %#v, %v", result, err)
|
|
}
|
|
if len(sender.requests) != 3 || sender.requests[0].Headers["Content-Type"] != "application/json" || sender.requests[0].Headers["User-Agent"] != UserAgent || sender.requests[0].Headers["X-Zhinian-Signature"] == "" {
|
|
t.Fatalf("requests = %#v", sender.requests)
|
|
}
|
|
}
|
|
|
|
func TestDeliverRespectsPersistedAttemptCeiling(t *testing.T) {
|
|
sender := &senderStub{}
|
|
deliverer := NewDeliverer(sender, "", nil)
|
|
result, err := deliverer.Deliver(context.Background(), jobs.Job{WebhookURL: "https://hooks.example.test", WebhookAttempts: 3})
|
|
if err != nil || result.Attempts != 3 || len(sender.requests) != 0 {
|
|
t.Fatalf("Deliver = %#v, %v requests=%d", result, err, len(sender.requests))
|
|
}
|
|
}
|
|
|
|
type sendResult struct {
|
|
response Response
|
|
err error
|
|
}
|
|
type senderStub struct {
|
|
responses []sendResult
|
|
requests []Request
|
|
}
|
|
|
|
func (stub *senderStub) Send(_ context.Context, request Request) (Response, error) {
|
|
stub.requests = append(stub.requests, request)
|
|
result := stub.responses[len(stub.requests)-1]
|
|
return result.response, result.err
|
|
}
|
|
|
|
type webhookFixture struct {
|
|
Secret string `json:"secret"`
|
|
Body string `json:"body"`
|
|
Signature string `json:"signature"`
|
|
Job struct {
|
|
ID string `json:"id"`
|
|
Status string `json:"status"`
|
|
Capability string `json:"capability"`
|
|
OutputAssetIDs []string `json:"outputAssetIds"`
|
|
Error *jobs.JobError `json:"error"`
|
|
UpdatedAt string `json:"updatedAt"`
|
|
} `json:"job"`
|
|
}
|
|
|
|
func loadWebhookFixture(t *testing.T) webhookFixture {
|
|
t.Helper()
|
|
_, filename, _, ok := runtime.Caller(0)
|
|
if !ok {
|
|
t.Fatal("locate fixture")
|
|
}
|
|
raw, err := os.ReadFile(filepath.Join(filepath.Dir(filename), "..", "..", "..", "contracts", "webhook", "job-webhook-v1.json"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var fixture webhookFixture
|
|
if err := json.Unmarshal(raw, &fixture); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return fixture
|
|
}
|