package runtime import ( "context" "errors" "os" "path/filepath" "runtime" "strings" "sync" "testing" "time" "browser.local/run/protocol" ) const dependencyTestDigest = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" type dependencyTestSupervisor struct { mu sync.Mutex calls [][]string present bool block bool } func (supervisor *dependencyTestSupervisor) Run(ctx context.Context, command ProcessCommand) (ProcessResult, error) { supervisor.mu.Lock() supervisor.calls = append(supervisor.calls, append([]string(nil), command.Args...)) block := supervisor.block present := supervisor.present if len(command.Args) > 0 && command.Args[0] == "apt-get" { supervisor.present = true present = true } supervisor.mu.Unlock() if block { <-ctx.Done() return ProcessResult{ExitCode: -1}, ctx.Err() } if len(command.Args) > 0 && command.Args[0] == "java" { if !present { return ProcessResult{ExitCode: 1}, errors.New("not installed") } return ProcessResult{ExitCode: 0, Stderr: "openjdk version 21.0.2"}, nil } return ProcessResult{ExitCode: 0}, nil } func (supervisor *dependencyTestSupervisor) count(name string) int { supervisor.mu.Lock() defer supervisor.mu.Unlock() count := 0 for _, call := range supervisor.calls { if len(call) > 0 && call[0] == name { count++ } } return count } type dependencyTestDownloader struct { payload []byte sourceURL string destination string } func (downloader *dependencyTestDownloader) Download(_ context.Context, sourceURL, destination string, _ int64) (int64, string, error) { downloader.sourceURL = sourceURL downloader.destination = destination if err := os.MkdirAll(filepath.Dir(destination), 0o700); err != nil { return 0, "", err } if err := os.WriteFile(destination, downloader.payload, 0o600); err != nil { return 0, "", err } return int64(len(downloader.payload)), bytesChecksum(downloader.payload), nil } func TestDependencyInstallExecutesTypedPlanAndResumesCompletedSteps(t *testing.T) { if !dependencyVersionAtLeast("openjdk version 21.0.2", "21") { t.Fatal("version comparator rejected valid Java version") } client := newFakeWorkerClient() assignment := dependencyAssignment(protocol.RunCapabilityDependenciesInstall) client.dependencyInput = dependencyInputForAssignment(assignment) runner := &dependencyTestSupervisor{} worker, err := NewWorker(workerTestConfig(t), client, WithDependencyCommandRunner(runner)) if err != nil { t.Fatalf("new worker: %v", err) } worker.state.SessionToken = "session-token" first := worker.executeDependencyJob(context.Background(), assignment) if first.State != lifecycleResultStateSucceeded || first.ExecutionResult.Kind != "dependency.install" || first.ExecutionResult.Checksum != dependencyTestDigest { state, evidence, probeErr := worker.executor.runDependencyProbe(context.Background(), client.dependencyInput.Probe, client.dependencyInput.Bindings) t.Fatalf("expected real typed dependency install, got %+v calls=%+v present=%v probe=%s evidence=%s err=%v", first, runner.calls, runner.present, state, evidence, probeErr) } second := worker.executeDependencyJob(context.Background(), assignment) if second.State != lifecycleResultStateSucceeded { t.Fatalf("expected journal resume success, got %+v", second) } if runner.count("apt-get") != 1 { t.Fatalf("completed package step must not repeat, calls=%+v", runner.calls) } if !strings.Contains(first.ExecutionResult.Content, `"completedSteps":1`) || strings.Contains(first.ExecutionResult.Content, "/Users/") { t.Fatalf("dependency evidence is not safe: %s", first.ExecutionResult.Content) } } func TestDependencyProbeAndInstallRejectUnsafeOrCancelledWork(t *testing.T) { client := newFakeWorkerClient() assignment := dependencyAssignment(protocol.RunCapabilityDependenciesInstall) input := dependencyInputForAssignment(assignment) input.Plan.Steps[0].PackageName = "openjdk;rm" client.dependencyInput = input worker, err := NewWorker(workerTestConfig(t), client, WithDependencyCommandRunner(&dependencyTestSupervisor{})) if err != nil { t.Fatalf("new worker: %v", err) } worker.state.SessionToken = "session-token" unsafe := worker.executeDependencyJob(context.Background(), assignment) if unsafe.State != lifecycleResultStateFailed || unsafe.ErrorCode != "dependency_install_failed" { t.Fatalf("expected unsafe package rejection, got %+v", unsafe) } check := dependencyAssignment(protocol.RunCapabilityDependenciesCheck) checkInput := dependencyInputForAssignment(check) checkInput.Plan = protocol.DependencyInstallPlan{} client.dependencyInput = checkInput blocking := &dependencyTestSupervisor{block: true} worker.executor.dependencyRunner = blocking ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond) defer cancel() cancelled := worker.executeDependencyJob(ctx, check) if cancelled.State != lifecycleResultStateCancelled || cancelled.ErrorCode != "dependency_probe_cancelled" { t.Fatalf("expected cancelled dependency probe, got %+v", cancelled) } } func TestVerifiedDependencyDownloadUsesHTTPSChecksumAndScopedDestination(t *testing.T) { payload := []byte("verified dependency") downloader := &dependencyTestDownloader{payload: payload} executor := NewLifecycleExecutor(WithLifecycleWorkspaceRoot(t.TempDir()), WithDependencyDownloader(downloader)) assignment := dependencyAssignment(protocol.RunCapabilityDependenciesInstall) input := dependencyInputForAssignment(assignment) step := protocol.DependencyInstallStep{Type: "verified-download", TargetKey: "tools/java", DownloadRef: "https://downloads.example.test/java", Checksum: bytesChecksum(payload)} if err := executor.runDependencyInstallStep(context.Background(), assignment, input, step, 0); err != nil { t.Fatalf("verified download: %v", err) } if downloader.sourceURL != step.DownloadRef || !strings.Contains(downloader.destination, "dependency-files") || strings.Contains(downloader.destination, "..") { t.Fatalf("unexpected scoped download: source=%s destination=%s", downloader.sourceURL, downloader.destination) } if _, err := validateDependencyDownloadURL("https://127.0.0.1/tool"); err == nil { t.Fatal("expected private dependency download host rejection") } } func dependencyAssignment(capability string) protocol.RunJobAssignment { assignment := workerJobAssignment(capability) assignment.LeaseToken = "lease-dependency" assignment.Attempt = 1 assignment.State = "running" if capability == protocol.RunCapabilityDependenciesInstall { assignment.TargetKey = "dependencies/install/install-java" } else { assignment.TargetKey = "dependencies/java-runtime" } return assignment } func dependencyInputForAssignment(assignment protocol.RunJobAssignment) protocol.DependencyExecutionInputResponse { return protocol.DependencyExecutionInputResponse{ JobID: assignment.JobID, ServerInstanceID: assignment.ServerInstanceID, RunEndpointID: assignment.RunEndpointID, PluginID: "game.minecraft", PluginVersion: "1.0.0", ProfileKey: "local", TargetOS: runtime.GOOS, TargetArch: runtime.GOARCH, PlanDigest: dependencyTestDigest, Probe: protocol.DependencyProbe{Key: "java-runtime", Kind: "java.version", TargetKey: "java", MinimumVersion: "21", Platforms: []string{runtime.GOOS}}, Plan: protocol.DependencyInstallPlan{Key: "install-java", Title: "Install Java", Platforms: []string{runtime.GOOS}, Steps: []protocol.DependencyInstallStep{{Type: "package", TargetKey: "java", PackageManager: "apt", PackageName: "openjdk-21-jre"}}}, Bindings: map[string]string{"java": "java"}, } }