Files
nezha_domains/integration/agentcompat/cmd/agentcompat/dedicated_wiring_test.go
T
2026-07-20 14:09:20 +00:00

155 lines
6.6 KiB
Go

//go:build linux
package main
import (
"context"
"errors"
"os"
"path/filepath"
"reflect"
"testing"
"time"
"github.com/nezhahq/nezha/integration/agentcompat/internal/contract"
"github.com/nezhahq/nezha/integration/agentcompat/internal/scenario"
)
func TestCLI_SelectsTransferAndReconnectWithTypedEvidence(t *testing.T) {
tests := []struct {
name string
fault string
}{
{contract.ScenarioTransfer100MiB, ""},
{contract.ScenarioTransfer100MiB, contract.FaultTransferHash},
{contract.ScenarioReconnect, ""},
{contract.ScenarioReconnect, contract.FaultDashboardExit},
}
for _, test := range tests {
t.Run(test.name+"/"+test.fault, func(t *testing.T) {
config := testCLIConfig(t, test.name, test.fault)
runnerErr := errors.New("dedicated runner sentinel")
wantResult := scenario.Result{Name: test.name, Passed: false, CleanupOK: true, Error: runnerErr.Error(), Assertions: []scenario.Assertion{{Name: "typed dispatch", Passed: false, Details: test.fault}}}
wantTransfer := scenario.TransferEvidence{WarmupUploadBytes: 65536, WarmupDownloadBytes: 65536, WarmupSHA256: "warmup", WarmupDuration: time.Second, WarmupDeadlineRemaining: 2 * time.Second, WarmupQuiescent: true, UploadBytes: contract.TransferBytes, DownloadBytes: contract.TransferBytes, UploadSHA256: "transfer-hash", DownloadSHA256: "transfer-hash", UploadChunks: 3, DownloadChunks: 4, UploadDuration: 5 * time.Second, DownloadDuration: 6 * time.Second, RetainedHeapBytes: 7, Mode: "0640", CreateDirs: true, UploadReplayRejected: true, DownloadReplayRejected: true, OversizeRejected: true, OutsideRootSentinelsUnchanged: true}
wantReconnect := completeReconnectDispatchEvidence(t)
previousTransfer := runTransferScenario
previousReconnect := runReconnectScenario
var receivedTransfer *scenario.TransferInput
var receivedReconnect *scenario.ReconnectInput
runTransferScenario = func(_ context.Context, input scenario.TransferInput) (scenario.Result, scenario.TransferEvidence, error) {
receivedTransfer = &input
return wantResult, wantTransfer, runnerErr
}
runReconnectScenario = func(_ context.Context, input scenario.ReconnectInput) (scenario.Result, scenario.ReconnectEvidence, error) {
receivedReconnect = &input
return wantResult, wantReconnect, runnerErr
}
t.Cleanup(func() {
runTransferScenario = previousTransfer
runReconnectScenario = previousReconnect
})
execution, err := selectScenarioExecution(config)
if err != nil {
t.Fatalf("select execution: %v", err)
}
output, runErr := execution.run(context.Background())
if !errors.Is(runErr, runnerErr) {
t.Fatalf("runner error=%v, want sentinel", runErr)
}
if err := output.Validate(); err != nil {
t.Fatalf("validate typed output: %v", err)
}
if !reflect.DeepEqual(output.Result, wantResult) {
t.Fatalf("result=%#v, want %#v", output.Result, wantResult)
}
if test.name == contract.ScenarioTransfer100MiB {
wantInput := scenario.TransferInput{Paths: config.Paths, Fault: config.Fault}
if receivedTransfer == nil || *receivedTransfer != wantInput || receivedReconnect != nil {
t.Fatalf("transfer inputs: received=%#v reconnect=%#v want=%#v", receivedTransfer, receivedReconnect, wantInput)
}
if output.Transfer == nil || !reflect.DeepEqual(*output.Transfer, wantTransfer) || output.Reconnect != nil {
t.Fatalf("transfer evidence=%#v reconnect=%#v", output.Transfer, output.Reconnect)
}
} else {
wantInput := scenario.ReconnectInput{Paths: config.Paths, DashboardFault: config.Fault.String()}
if receivedReconnect == nil || *receivedReconnect != wantInput || receivedTransfer != nil {
t.Fatalf("reconnect inputs: received=%#v transfer=%#v want=%#v", receivedReconnect, receivedTransfer, wantInput)
}
if output.Reconnect == nil || !reflect.DeepEqual(*output.Reconnect, wantReconnect) || output.Transfer != nil {
t.Fatalf("reconnect evidence=%#v transfer=%#v", output.Reconnect, output.Transfer)
}
}
})
}
}
func TestCLI_RejectsUnsupportedScenarioFaultPairsBeforeRunner(t *testing.T) {
tests := []struct{ scenario, fault string }{
{contract.ScenarioTransfer100MiB, contract.FaultDashboardExit},
{contract.ScenarioReconnect, contract.FaultTransferHash},
{contract.ScenarioMCPFilesystem, contract.FaultAgentBadSecret},
}
for _, test := range tests {
called := false
previousTransfer := runTransferScenario
runTransferScenario = func(context.Context, scenario.TransferInput) (scenario.Result, scenario.TransferEvidence, error) {
called = true
return scenario.Result{}, scenario.TransferEvidence{}, errors.New("unexpected")
}
_, err := selectScenarioExecution(testCLIConfig(t, test.scenario, test.fault))
runTransferScenario = previousTransfer
if err == nil || called {
t.Fatalf("unsupported pair started runner: scenario=%q fault=%q err=%v", test.scenario, test.fault, err)
}
}
}
func TestCLI_WritesDedicatedArtifactWithPrivateMode(t *testing.T) {
resultsDir := t.TempDir()
output := scenarioExecutionOutput{
Result: scenario.Result{Name: contract.ScenarioTransfer100MiB, Passed: false, CleanupOK: true, Error: "transfer scenario: injected hash mismatch"},
Transfer: &scenario.TransferEvidence{WarmupUploadBytes: 65536, WarmupDownloadBytes: 65536, WarmupSHA256: "abc", WarmupDuration: time.Nanosecond, WarmupDeadlineRemaining: time.Second, WarmupQuiescent: true, OutsideRootSentinelsUnchanged: true},
}
config := testCLIConfig(t, contract.ScenarioTransfer100MiB, contract.FaultTransferHash)
paths, err := contract.NewPaths(config.Paths.NezhaSource().String(), config.Paths.AgentSource().String(), resultsDir)
if err != nil {
t.Fatalf("paths: %v", err)
}
config.Paths = paths
if err := writeScenarioEvidence(config, output, time.Now()); err != nil {
t.Fatalf("write evidence: %v", err)
}
info, err := os.Stat(filepath.Join(resultsDir, "transfer.json"))
if err != nil {
t.Fatalf("stat transfer evidence: %v", err)
}
if info.Mode().Perm() != 0o600 {
t.Fatalf("transfer evidence mode=%o", info.Mode().Perm())
}
}
func testCLIConfig(t *testing.T, scenarioName, faultName string) cliConfig {
t.Helper()
paths, err := contract.NewPaths("/src/nezha", "/src/agent", t.TempDir())
if err != nil {
t.Fatalf("paths: %v", err)
}
profile, err := contract.ProfileByName("pr-full")
if err != nil {
t.Fatalf("profile: %v", err)
}
scenarioValue, err := contract.NewScenario(scenarioName)
if err != nil {
t.Fatalf("scenario: %v", err)
}
fault := contract.Fault{}
if faultName != "" {
fault, err = contract.NewFault(faultName)
if err != nil {
t.Fatalf("fault: %v", err)
}
}
return cliConfig{Paths: paths, Profile: profile, Seed: contract.DefaultSeed, Scenarios: scenarioFlags{scenarioValue}, Fault: fault}
}