package agent import ( "bytes" "context" "crypto/sha256" "encoding/hex" "errors" "fmt" "io" "net/http" "net/http/httptest" "os" "strings" "sync/atomic" "testing" "time" agentpb "git.ipao.vip/rogee/go-sip/gen/agent" ) func TestUploadBytesPutsMemoryRecordingOnceWithoutBusinessFile(t *testing.T) { body := []byte("recording held only in memory") sum := sha256.Sum256(body) var requests atomic.Int32 var received []byte server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requests.Add(1) if r.Method != http.MethodPut || r.ContentLength != int64(len(body)) || r.Header.Get("x-upload-token") != "mock-token" { http.Error(w, "invalid presigned PUT", http.StatusBadRequest) return } received, _ = io.ReadAll(r.Body) w.Header().Set("ETag", "mock-etag") })) defer server.Close() noFiles := t.TempDir() t.Setenv("TMPDIR", noFiles) grant := &agentpb.UploadGrant{UploadId: "upload-mem", ObjectKey: "recordings/call-1.wav", TargetUrl: server.URL + "/file?signature=DO_NOT_LOG", ExpiresAtUnixMs: time.Now().Add(time.Minute).UnixMilli(), MaxBytes: int64(len(body)), RequiredChecksumSha256: hex.EncodeToString(sum[:]), Headers: []*agentpb.Header{{Name: "x-upload-token", Value: "mock-token"}}} result, err := (UploadClient{AllowInsecureHTTP: true}).UploadBytes(context.Background(), grant, body) if err != nil || requests.Load() != 1 || !bytes.Equal(received, body) || result.SHA256 != grant.RequiredChecksumSha256 || result.SizeBytes != int64(len(body)) || result.ETag != "mock-etag" { t.Fatalf("normal path must PUT original bytes once: result=%+v requests=%d err=%v", result, requests.Load(), err) } entries, err := os.ReadDir(noFiles) if err != nil || len(entries) != 0 { t.Fatalf("normal path created a business file: entries=%v err=%v", entries, err) } } func TestUploadBytesRejectsUnchangedGrantMismatchBeforePUT(t *testing.T) { var requests atomic.Int32 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { requests.Add(1) })) defer server.Close() data := []byte("original recording") grant := &agentpb.UploadGrant{UploadId: "upload-mismatch", ObjectKey: "recordings/call-2.wav", TargetUrl: server.URL, ExpiresAtUnixMs: time.Now().Add(time.Minute).UnixMilli(), MaxBytes: int64(len(data)), RequiredChecksumSha256: strings.Repeat("f", 64)} _, err := (UploadClient{AllowInsecureHTTP: true}).UploadBytes(context.Background(), grant, data) if !errors.Is(err, ErrUploadChecksumMismatch) || requests.Load() != 0 || string(data) != "original recording" { t.Fatalf("mismatched grant caused a PUT or changed recording: requests=%d err=%v", requests.Load(), err) } } func TestUploadBytesDefinitiveHTTPFailureIsOneAttempt(t *testing.T) { var requests atomic.Int32 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { requests.Add(1) w.WriteHeader(http.StatusServiceUnavailable) _, _ = io.WriteString(w, "DO_NOT_LOG_RESPONSE") })) defer server.Close() grant := &agentpb.UploadGrant{UploadId: "upload-fail", ObjectKey: "recordings/call-3.wav", TargetUrl: server.URL, ExpiresAtUnixMs: time.Now().Add(time.Minute).UnixMilli(), MaxBytes: 100} _, err := (UploadClient{AllowInsecureHTTP: true}).UploadBytes(context.Background(), grant, []byte("recording")) var rejected *UploadHTTPError if !errors.As(err, &rejected) || rejected.StatusCode != http.StatusServiceUnavailable || requests.Load() != 1 || strings.Contains(err.Error(), "DO_NOT_LOG_RESPONSE") { t.Fatalf("failed OSS PUT was retried or leaked its response: requests=%d err=%v", requests.Load(), err) } } type ambiguousBytesTransport struct{ requests *atomic.Int32 } func (t ambiguousBytesTransport) RoundTrip(req *http.Request) (*http.Response, error) { t.requests.Add(1) return nil, fmt.Errorf("transport says URL=%s", req.URL.String()) } func TestUploadBytesAmbiguousTransportFailureIsNotRetriedOrLeaked(t *testing.T) { var requests atomic.Int32 client := &http.Client{Transport: ambiguousBytesTransport{requests: &requests}} grant := &agentpb.UploadGrant{UploadId: "upload-unknown", ObjectKey: "recordings/call-4.wav", TargetUrl: "https://oss.example.invalid/file?signature=DO_NOT_LOG", ExpiresAtUnixMs: time.Now().Add(time.Minute).UnixMilli(), MaxBytes: 100} _, err := (UploadClient{HTTPClient: client}).UploadBytes(context.Background(), grant, []byte("recording")) if !errors.Is(err, ErrUploadOutcomeUnknown) || requests.Load() != 1 || strings.Contains(err.Error(), "DO_NOT_LOG") || strings.Contains(err.Error(), "signature=") { t.Fatalf("ambiguous upload must halt without token disclosure: requests=%d err=%v", requests.Load(), err) } } func TestUploadBytesRejectsNilGrantHeaderBeforePUT(t *testing.T) { grant := &agentpb.UploadGrant{UploadId: "upload-bad-header", ObjectKey: "recordings/call-5.wav", TargetUrl: "https://oss.example.invalid/file", ExpiresAtUnixMs: time.Now().Add(time.Minute).UnixMilli(), MaxBytes: 100, Headers: []*agentpb.Header{nil}} _, err := (UploadClient{}).UploadBytes(context.Background(), grant, []byte("recording")) if !errors.Is(err, ErrUploadGrantInvalid) { t.Fatalf("malformed grant header must be rejected without a panic: %v", err) } }