Files
go-sip/internal/agent/upload_bytes_test.go
T

114 lines
6.0 KiB
Go

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", Bucket: "mock-bucket", 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 TestUploadBytesRequiresOriginalBucketBeforePUT(t *testing.T) {
var requests atomic.Int32
server := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { requests.Add(1) }))
defer server.Close()
body := []byte("recording")
sum := sha256.Sum256(body)
grant := &agentpb.UploadGrant{UploadId: "upload-no-bucket", ObjectKey: "recordings/call.wav", TargetUrl: server.URL, ExpiresAtUnixMs: time.Now().Add(time.Minute).UnixMilli(), MaxBytes: int64(len(body)), RequiredChecksumSha256: hex.EncodeToString(sum[:])}
if _, err := (UploadClient{AllowInsecureHTTP: true}).UploadBytes(context.Background(), grant, body); !errors.Is(err, ErrUploadGrantInvalid) || requests.Load() != 0 {
t.Fatalf("missing D-owned original bucket sent a PUT: attempts=%d err=%v", requests.Load(), 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", Bucket: "mock-bucket", 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", Bucket: "mock-bucket", 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", Bucket: "mock-bucket", 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", Bucket: "mock-bucket", 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)
}
}