Files
go-sip/internal/rpc/dispatcher_upload_integration_test.go
T

126 lines
5.6 KiB
Go

package rpc
import (
"context"
"crypto/sha256"
"encoding/hex"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"time"
agentpb "git.ipao.vip/rogee/go-sip/gen/agent"
"git.ipao.vip/rogee/go-sip/internal/agent"
ossclient "git.ipao.vip/rogee/go-sip/internal/oss"
"git.ipao.vip/rogee/go-sip/internal/store"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
func TestAlibabaOSSDispatcherUploadDurableIntegration(t *testing.T) {
if os.Getenv("AGENT_CALL_OSS_INTEGRATION") != "1" {
t.Skip("set AGENT_CALL_OSS_INTEGRATION=1 to use the authorized Alibaba OSS test bucket")
}
client, err := ossclient.NewClient(ossclient.Config{
Endpoint: os.Getenv("DISPATCHER_OSS_ENDPOINT"),
Region: os.Getenv("DISPATCHER_OSS_REGION"),
Bucket: os.Getenv("DISPATCHER_OSS_BUCKET"),
AccessKeyID: os.Getenv("DISPATCHER_OSS_ACCESS_KEY_ID"),
AccessKeySecret: os.Getenv("DISPATCHER_OSS_ACCESS_KEY_SECRET"),
KeyPrefix: "agent-call/rpc-integration-tests",
GrantTTL: 15 * time.Minute,
MaxAssetBytes: 1 << 20,
})
if err != nil {
t.Fatal(err)
}
path := filepath.Join(t.TempDir(), "recording.wav")
payload := []byte(strings.Repeat("dispatcher-upload-integration\n", 1024))
if sourcePath := os.Getenv("OSS_INTEGRATION_RECORDING_PATH"); sourcePath != "" {
var readErr error
payload, readErr = os.ReadFile(sourcePath)
if readErr != nil {
t.Fatal(readErr)
}
if len(payload) == 0 {
t.Fatal("OSS_INTEGRATION_RECORDING_PATH is empty")
}
}
if err := os.WriteFile(path, payload, 0o600); err != nil {
t.Fatal(err)
}
digest := sha256.Sum256(payload)
checksum := hex.EncodeToString(digest[:])
binding := &agentpb.ExecutionBinding{TenantId: "tenant-integration", TenantKey: "tenant-integration", ExecutionId: "exec-oss-integration", TaskId: "task-oss-integration", TaskItemId: "item-oss-integration", TaskRevision: 1, CallId: "call-oss-integration", AttemptId: "attempt-oss-integration"}
asset := &agentpb.AssetDescriptor{Kind: agentpb.AssetKind_ASSET_KIND_RECORDING, AssetId: "recording-integration", CallId: binding.CallId, ExecutionId: binding.ExecutionId, Format: "wav", SizeBytes: int64(len(payload)), ChecksumSha256: checksum, Channels: 1, SampleRateHz: 16000, DurationMs: 1000}
requestMeta := &agentpb.RequestMeta{ProtocolVersion: "agent.v1", RequestId: "request-oss-integration", TraceId: "trace-oss-integration", OperationId: "operation-oss-integration", IdempotencyKey: "idempotency-oss-integration", AgentId: "agent-integration", CellId: "cell-integration"}
st, err := store.Open(filepath.Join(t.TempDir(), "dispatcher.db"))
if err != nil {
t.Fatal(err)
}
defer st.Close()
if err := st.BindDispatcherID("11111111-1111-4111-8111-111111111111"); err != nil {
t.Fatal(err)
}
clock := time.Now()
server, err := NewDispatcherUploadServer(st, client, func() time.Time { return clock }, false)
if err != nil {
t.Fatal(err)
}
uploadID := "upload-oss-integration"
grantResponse, err := server.RequestUpload(context.Background(), &agentpb.RequestUploadRequest{Meta: requestMeta, Binding: binding, Asset: asset, UploadId: uploadID})
if err != nil {
t.Fatal(err)
}
if grantResponse.Receipt.Result != agentpb.ResultCode_RESULT_CODE_ACCEPTED || grantResponse.Grant == nil {
t.Fatalf("grant rejected: %+v", grantResponse.Receipt)
}
clock = clock.Add(16 * time.Minute)
requestMeta = proto.Clone(requestMeta).(*agentpb.RequestMeta)
requestMeta.OperationId = "renew-upload-" + uploadID
requestMeta.IdempotencyKey = "renew-upload-key-" + uploadID
retryMeta := proto.Clone(requestMeta).(*agentpb.RequestMeta)
retryMeta.RequestId = "request-oss-integration-retry"
retryMeta.TraceId = "trace-oss-integration-retry"
retryMeta.OperationId = "operation-oss-integration-retry"
retryMeta.IdempotencyKey = "idempotency-oss-integration-retry"
reissuedResponse, err := server.RequestUpload(context.Background(), &agentpb.RequestUploadRequest{Meta: retryMeta, Binding: binding, Asset: asset, UploadId: uploadID})
if err != nil {
t.Fatal(err)
}
if reissuedResponse.Grant.ExpiresAtUnixMs <= grantResponse.Grant.ExpiresAtUnixMs {
t.Fatal("explicit post-expiry request did not receive a new grant")
}
grantResponse = reissuedResponse
parsed, err := url.Parse(grantResponse.Grant.TargetUrl)
if err != nil {
t.Fatal(err)
}
uploader := agent.UploadClient{Now: time.Now, AllowedHosts: map[string]struct{}{strings.ToLower(parsed.Host): {}}}
result, err := uploader.UploadFile(context.Background(), grantResponse.Grant, path)
if err != nil {
t.Fatal(err)
}
completion := &agentpb.CompleteUploadRequest{Meta: requestMeta, Binding: binding, Asset: asset, UploadId: uploadID, UploadedSizeBytes: result.SizeBytes, UploadedChecksumSha256: result.SHA256}
for i := 0; i < 2; i++ {
if _, err := server.CompleteUpload(context.Background(), completion); status.Code(err) != codes.Unavailable {
t.Fatalf("notification without MQ publication must remain pending: %v", err)
}
}
var outboxCount int
if err := st.DB().QueryRow(`SELECT COUNT(*) FROM upload_notifications WHERE upload_id=?`, uploadID).Scan(&outboxCount); err != nil {
t.Fatal(err)
}
if outboxCount != 1 {
t.Fatalf("upload notification rows = %d, want 1", outboxCount)
}
if _, err := server.RequestUpload(context.Background(), &agentpb.RequestUploadRequest{Meta: requestMeta, Binding: binding, Asset: asset, UploadId: uploadID}); status.Code(err) != codes.Unavailable {
t.Fatal("uploaded object was reauthorized")
}
// This opt-in test establishes the upload fact only. Queue-delivery evidence
// comes from isolated RabbitMQ tests, not this real-object-storage test.
}