126 lines
5.6 KiB
Go
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.
|
|
}
|