notify/file_send_progress_test.go
starainrt 09d972c7b7
feat(notify): 重构通信内核并补齐 stream/bulk/record/transfer 能力
- 引入 LogicalConn/TransportConn 分层,ClientConn 保留兼容适配层
  - 新增 Stream、Bulk、RecordStream 三条数据面能力及对应控制路径
  - 完成 transfer/file 传输内核与状态快照、诊断能力
  - 补齐 reconnect、inbound dispatcher、modern psk 等基础模块
  - 增加大规模回归、并发与基准测试覆盖
  - 更新依赖库
2026-04-15 15:24:36 +08:00

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)
}
}