225 lines
7.6 KiB
Go
225 lines
7.6 KiB
Go
|
|
package notify
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestFileSendSessionProgress(t *testing.T) {
|
||
|
|
session := &fileSendSession{
|
||
|
|
fileID: "file-1",
|
||
|
|
path: "/tmp/demo.bin",
|
||
|
|
name: "demo.bin",
|
||
|
|
size: 200,
|
||
|
|
checksum: "sum",
|
||
|
|
startedAt: time.Unix(100, 0),
|
||
|
|
updatedAt: time.Unix(100, 0),
|
||
|
|
}
|
||
|
|
|
||
|
|
metaEvent := session.onMetaSent(time.Unix(100, 0))
|
||
|
|
if got, want := metaEvent.Kind, EnvelopeFileMeta; got != want {
|
||
|
|
t.Fatalf("meta kind mismatch: got %v want %v", got, want)
|
||
|
|
}
|
||
|
|
if got, want := metaEvent.Total, int64(200); got != want {
|
||
|
|
t.Fatalf("meta total mismatch: got %d want %d", got, want)
|
||
|
|
}
|
||
|
|
if got, want := metaEvent.Received, int64(0); got != want {
|
||
|
|
t.Fatalf("meta received mismatch: got %d want %d", got, want)
|
||
|
|
}
|
||
|
|
|
||
|
|
chunkEvent, err := session.onChunkSent(0, 80, time.Unix(104, 0))
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("onChunkSent failed: %v", err)
|
||
|
|
}
|
||
|
|
if got, want := chunkEvent.Received, int64(80); got != want {
|
||
|
|
t.Fatalf("chunk received mismatch: got %d want %d", got, want)
|
||
|
|
}
|
||
|
|
if got, want := chunkEvent.Percent, 40.0; got != want {
|
||
|
|
t.Fatalf("chunk percent mismatch: got %v want %v", got, want)
|
||
|
|
}
|
||
|
|
if got, want := chunkEvent.Duration, 4*time.Second; got != want {
|
||
|
|
t.Fatalf("chunk duration mismatch: got %v want %v", got, want)
|
||
|
|
}
|
||
|
|
if got, want := chunkEvent.StepDuration, 4*time.Second; got != want {
|
||
|
|
t.Fatalf("chunk step duration mismatch: got %v want %v", got, want)
|
||
|
|
}
|
||
|
|
if got, want := chunkEvent.InstantRateBPS, 20.0; got != want {
|
||
|
|
t.Fatalf("chunk instant rate mismatch: got %v want %v", got, want)
|
||
|
|
}
|
||
|
|
|
||
|
|
secondChunkEvent, err := session.onChunkSent(80, 120, time.Unix(108, 0))
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("second onChunkSent failed: %v", err)
|
||
|
|
}
|
||
|
|
if got, want := secondChunkEvent.Received, int64(200); got != want {
|
||
|
|
t.Fatalf("second chunk received mismatch: got %d want %d", got, want)
|
||
|
|
}
|
||
|
|
if got, want := secondChunkEvent.Percent, 100.0; got != want {
|
||
|
|
t.Fatalf("second chunk percent mismatch: got %v want %v", got, want)
|
||
|
|
}
|
||
|
|
if got, want := secondChunkEvent.RateBPS, 25.0; got != want {
|
||
|
|
t.Fatalf("second chunk rate mismatch: got %v want %v", got, want)
|
||
|
|
}
|
||
|
|
if got, want := secondChunkEvent.StepDuration, 4*time.Second; got != want {
|
||
|
|
t.Fatalf("second chunk step duration mismatch: got %v want %v", got, want)
|
||
|
|
}
|
||
|
|
if got, want := secondChunkEvent.InstantRateBPS, 30.0; got != want {
|
||
|
|
t.Fatalf("second chunk instant rate mismatch: got %v want %v", got, want)
|
||
|
|
}
|
||
|
|
|
||
|
|
endEvent := session.onEndSent(time.Unix(110, 0))
|
||
|
|
if !endEvent.Done {
|
||
|
|
t.Fatal("end event should be done")
|
||
|
|
}
|
||
|
|
if got, want := endEvent.Received, int64(200); got != want {
|
||
|
|
t.Fatalf("end received mismatch: got %d want %d", got, want)
|
||
|
|
}
|
||
|
|
if got, want := endEvent.Percent, 100.0; got != want {
|
||
|
|
t.Fatalf("end percent mismatch: got %v want %v", got, want)
|
||
|
|
}
|
||
|
|
if got, want := endEvent.Duration, 10*time.Second; got != want {
|
||
|
|
t.Fatalf("end duration mismatch: got %v want %v", got, want)
|
||
|
|
}
|
||
|
|
if got, want := endEvent.StepDuration, 2*time.Second; got != want {
|
||
|
|
t.Fatalf("end step duration mismatch: got %v want %v", got, want)
|
||
|
|
}
|
||
|
|
if got, want := endEvent.RateBPS, 20.0; got != want {
|
||
|
|
t.Fatalf("end rate mismatch: got %v want %v", got, want)
|
||
|
|
}
|
||
|
|
if got, want := endEvent.InstantRateBPS, 0.0; got != want {
|
||
|
|
t.Fatalf("end instant rate mismatch: got %v want %v", got, want)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSendFileWithHooksLogsLocalProgress(t *testing.T) {
|
||
|
|
dir := t.TempDir()
|
||
|
|
filePath := filepath.Join(dir, "demo.txt")
|
||
|
|
data := []byte("hello notify send progress")
|
||
|
|
if err := os.WriteFile(filePath, data, 0o644); err != nil {
|
||
|
|
t.Fatalf("WriteFile failed: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
var sentKinds []EnvelopeKind
|
||
|
|
var events []FileEvent
|
||
|
|
err := sendFileWithHooks(context.Background(), filePath, fileSendHooks{
|
||
|
|
sendReliable: func(ctx context.Context, env Envelope) error {
|
||
|
|
sentKinds = append(sentKinds, env.Kind)
|
||
|
|
return nil
|
||
|
|
},
|
||
|
|
sendAbort: func(fileID string, stage string, offset int64, cause error) error {
|
||
|
|
t.Fatalf("unexpected abort: fileID=%s stage=%s offset=%d err=%v", fileID, stage, offset, cause)
|
||
|
|
return nil
|
||
|
|
},
|
||
|
|
publishEvent: func(event FileEvent) {
|
||
|
|
events = append(events, event)
|
||
|
|
},
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("sendFileWithHooks failed: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if got, want := len(sentKinds), 3; got != want {
|
||
|
|
t.Fatalf("sent kinds count mismatch: got %d want %d", got, want)
|
||
|
|
}
|
||
|
|
if sentKinds[0] != EnvelopeFileMeta || sentKinds[1] != EnvelopeFileChunk || sentKinds[2] != EnvelopeFileEnd {
|
||
|
|
t.Fatalf("unexpected sent kinds: %v", sentKinds)
|
||
|
|
}
|
||
|
|
|
||
|
|
if got, want := len(events), 3; got != want {
|
||
|
|
t.Fatalf("event count mismatch: got %d want %d", got, want)
|
||
|
|
}
|
||
|
|
if events[0].Kind != EnvelopeFileMeta || events[1].Kind != EnvelopeFileChunk || events[2].Kind != EnvelopeFileEnd {
|
||
|
|
t.Fatalf("unexpected event kinds: %+v", []EnvelopeKind{events[0].Kind, events[1].Kind, events[2].Kind})
|
||
|
|
}
|
||
|
|
if got, want := events[1].Received, int64(len(data)); got != want {
|
||
|
|
t.Fatalf("chunk received mismatch: got %d want %d", got, want)
|
||
|
|
}
|
||
|
|
if !events[2].Done {
|
||
|
|
t.Fatal("end event should be done")
|
||
|
|
}
|
||
|
|
if got, want := events[2].Received, int64(len(data)); got != want {
|
||
|
|
t.Fatalf("end received mismatch: got %d want %d", got, want)
|
||
|
|
}
|
||
|
|
if got, want := events[2].Path, filePath; got != want {
|
||
|
|
t.Fatalf("end path mismatch: got %q want %q", got, want)
|
||
|
|
}
|
||
|
|
if events[0].Packet.FileID == "" {
|
||
|
|
t.Fatal("fileID should not be empty")
|
||
|
|
}
|
||
|
|
if events[0].Packet.FileID != events[1].Packet.FileID || events[1].Packet.FileID != events[2].Packet.FileID {
|
||
|
|
t.Fatalf("fileID should stay stable across events: %+v", events)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSendFileWithHooksAbortOnChunkFailure(t *testing.T) {
|
||
|
|
dir := t.TempDir()
|
||
|
|
filePath := filepath.Join(dir, "demo.txt")
|
||
|
|
data := []byte("hello notify send failure")
|
||
|
|
if err := os.WriteFile(filePath, data, 0o644); err != nil {
|
||
|
|
t.Fatalf("WriteFile failed: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
wantErr := errors.New("chunk ack timeout")
|
||
|
|
var abortFileID string
|
||
|
|
var abortStage string
|
||
|
|
var abortOffset int64
|
||
|
|
var abortCause error
|
||
|
|
var events []FileEvent
|
||
|
|
|
||
|
|
err := sendFileWithHooks(context.Background(), filePath, fileSendHooks{
|
||
|
|
sendReliable: func(ctx context.Context, env Envelope) error {
|
||
|
|
if env.Kind == EnvelopeFileChunk {
|
||
|
|
return wantErr
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
},
|
||
|
|
sendAbort: func(fileID string, stage string, offset int64, cause error) error {
|
||
|
|
abortFileID = fileID
|
||
|
|
abortStage = stage
|
||
|
|
abortOffset = offset
|
||
|
|
abortCause = cause
|
||
|
|
return nil
|
||
|
|
},
|
||
|
|
publishEvent: func(event FileEvent) {
|
||
|
|
events = append(events, event)
|
||
|
|
},
|
||
|
|
})
|
||
|
|
if !errors.Is(err, wantErr) {
|
||
|
|
t.Fatalf("sendFileWithHooks error mismatch: got %v want %v", err, wantErr)
|
||
|
|
}
|
||
|
|
if abortFileID == "" {
|
||
|
|
t.Fatal("abort should capture fileID")
|
||
|
|
}
|
||
|
|
if got, want := abortStage, "chunk"; got != want {
|
||
|
|
t.Fatalf("abort stage mismatch: got %q want %q", got, want)
|
||
|
|
}
|
||
|
|
if got, want := abortOffset, int64(0); got != want {
|
||
|
|
t.Fatalf("abort offset mismatch: got %d want %d", got, want)
|
||
|
|
}
|
||
|
|
if !errors.Is(abortCause, wantErr) {
|
||
|
|
t.Fatalf("abort cause mismatch: got %v want %v", abortCause, wantErr)
|
||
|
|
}
|
||
|
|
if got, want := len(events), 2; got != want {
|
||
|
|
t.Fatalf("event count mismatch: got %d want %d", got, want)
|
||
|
|
}
|
||
|
|
if got, want := events[0].Kind, EnvelopeFileMeta; got != want {
|
||
|
|
t.Fatalf("first event kind mismatch: got %v want %v", got, want)
|
||
|
|
}
|
||
|
|
if got, want := events[1].Kind, EnvelopeFileAbort; got != want {
|
||
|
|
t.Fatalf("abort event kind mismatch: got %v want %v", got, want)
|
||
|
|
}
|
||
|
|
if got, want := events[1].Packet.Stage, "chunk"; got != want {
|
||
|
|
t.Fatalf("abort packet stage mismatch: got %q want %q", got, want)
|
||
|
|
}
|
||
|
|
if got, want := events[1].Received, int64(0); got != want {
|
||
|
|
t.Fatalf("abort received mismatch: got %d want %d", got, want)
|
||
|
|
}
|
||
|
|
if !errors.Is(events[1].Err, wantErr) {
|
||
|
|
t.Fatalf("abort event error mismatch: got %v want %v", events[1].Err, wantErr)
|
||
|
|
}
|
||
|
|
}
|