package launch import ( "errors" "os" "path/filepath" "testing" "softbox.local/core/storage" ) func TestServiceStartsOnlyVerifiedCurrentEntrypoint(t *testing.T) { store, appRoot := seedInstalledApp(t) launcher := &recordingLauncher{pid: 42} service := newService(t, store, launcher) result, err := service.Start(Request{AppID: "test-app"}) if err != nil { t.Fatalf("Start() error = %v", err) } if result != (Result{AppID: "test-app", PID: 42}) { t.Fatalf("result = %#v", result) } wantEntrypoint := filepath.Join(appRoot, "current", "bin", "App.exe") if launcher.command.Entrypoint != wantEntrypoint || launcher.command.WorkingDirectory != filepath.Join(appRoot, "current", "bin") || !launcher.command.RequiresAdmin { t.Fatalf("command = %#v", launcher.command) } } func TestServiceRejectsUnsafeOrUnavailableLaunchStates(t *testing.T) { errCompatibility := errors.New("compatibility unavailable") errAuthorization := errors.New("authorization unavailable") errTargetState := errors.New("target state unavailable") errStart := errors.New("start failed") tests := []struct { name string mutate func(*storage.InstalledApp, string) service func(*Service) wantErr error wantCode FailureCode }{ { name: "missing legacy metadata", mutate: func(record *storage.InstalledApp, _ string) { record.Entrypoint = "" }, wantErr: ErrLaunchMetadata, wantCode: FailureCodeLaunchMetadataInvalid, }, { name: "entrypoint is absent", mutate: func(_ *storage.InstalledApp, appRoot string) { if err := os.Remove(filepath.Join(appRoot, "current", "bin", "App.exe")); err != nil { t.Fatalf("remove entrypoint: %v", err) } }, wantErr: ErrEntrypointMissing, wantCode: FailureCodeEntrypointMissing, }, { name: "unsafe current layout", service: func(service *Service) { service.records = resolverFunc(func(string) (storage.InstalledApp, string, error) { return storage.InstalledApp{}, "", storage.ErrStorageLayoutUnsafe }) }, wantErr: ErrLaunchTargetUnsafe, wantCode: FailureCodeLaunchTargetUnsafe, }, { name: "incompatible system", service: func(service *Service) { service.compatibility = compatibilityFunc(func(string) (bool, error) { return false, nil }) }, wantErr: ErrAppIncompatible, wantCode: FailureCodeAppIncompatible, }, { name: "compatibility failure", service: func(service *Service) { service.compatibility = compatibilityFunc(func(string) (bool, error) { return false, errCompatibility }) }, wantErr: errCompatibility, wantCode: FailureCodeCompatibilityUnavailable, }, { name: "unauthorized", service: func(service *Service) { service.authorization = authorizationFunc(func(string) (bool, error) { return false, nil }) }, wantErr: ErrLaunchUnauthorized, wantCode: FailureCodeLaunchUnauthorized, }, { name: "authorization failure", service: func(service *Service) { service.authorization = authorizationFunc(func(string) (bool, error) { return false, errAuthorization }) }, wantErr: errAuthorization, wantCode: FailureCodeAuthorizationFailed, }, { name: "already running", service: func(service *Service) { service.targetState = targetStateFunc(func(string, string) (bool, error) { return true, nil }) }, wantErr: ErrAppRunning, wantCode: FailureCodeAppRunning, }, { name: "target state failure", service: func(service *Service) { service.targetState = targetStateFunc(func(string, string) (bool, error) { return false, errTargetState }) }, wantErr: errTargetState, wantCode: FailureCodeTargetStateUnavailable, }, { name: "launcher failure", service: func(service *Service) { service.launcher = &recordingLauncher{err: errStart} }, wantErr: errStart, wantCode: FailureCodeLaunchFailed, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { store, appRoot := seedInstalledApp(t) record, found, err := store.Read("test-app") if err != nil || !found { t.Fatalf("Read() found=%t err=%v", found, err) } if test.mutate != nil { test.mutate(&record, appRoot) if err := store.Write(record); err != nil { t.Fatalf("Write() error = %v", err) } } launcher := &recordingLauncher{pid: 42} service := newService(t, store, launcher) if test.service != nil { test.service(service) } _, err = service.Start(Request{AppID: "test-app"}) if !errors.Is(err, test.wantErr) { t.Fatalf("Start() error = %v, want %v", err, test.wantErr) } if code := launchErrorCode(t, err); code != test.wantCode { t.Fatalf("code = %q, want %q", code, test.wantCode) } if launcher.calls != 0 { t.Fatalf("launcher calls = %d, want 0", launcher.calls) } }) } } func TestNewServiceRequiresEveryDependency(t *testing.T) { store, _ := seedInstalledApp(t) config := ServiceConfig{ Records: store, Compatibility: compatibilityFunc(func(string) (bool, error) { return true, nil }), Authorization: authorizationFunc(func(string) (bool, error) { return true, nil }), TargetState: targetStateFunc(func(string, string) (bool, error) { return false, nil }), Launcher: &recordingLauncher{pid: 1}, } for _, test := range []struct { name string mutate func(*ServiceConfig) }{ {"records", func(config *ServiceConfig) { config.Records = nil }}, {"compatibility", func(config *ServiceConfig) { config.Compatibility = nil }}, {"authorization", func(config *ServiceConfig) { config.Authorization = nil }}, {"target state", func(config *ServiceConfig) { config.TargetState = nil }}, {"launcher", func(config *ServiceConfig) { config.Launcher = nil }}, } { t.Run(test.name, func(t *testing.T) { candidate := config test.mutate(&candidate) if _, err := NewService(candidate); !errors.Is(err, ErrLaunchConfig) { t.Fatalf("NewService() error = %v, want %v", err, ErrLaunchConfig) } }) } } func seedInstalledApp(t *testing.T) (*storage.InstalledAppStore, string) { t.Helper() appsRoot := filepath.Join(t.TempDir(), "apps") store := storage.NewInstalledAppStore(appsRoot) record := storage.InstalledApp{ SchemaVersion: 1, ID: "test-app", Version: "1.2.3", Architecture: "amd64", Channel: "stable", Entrypoint: "bin/App.exe", WorkingDirectory: "bin", MinOS: "windows-10", RequiresAdmin: true, Files: []storage.InstalledFile{{ Path: "bin/App.exe", Size: 1, SHA256: "0000000000000000000000000000000000000000000000000000000000000000", }}, } if err := store.Write(record); err != nil { t.Fatalf("Write() error = %v", err) } appRoot := filepath.Join(appsRoot, record.ID) entrypoint := filepath.Join(appRoot, "current", "bin", "App.exe") if err := os.MkdirAll(filepath.Dir(entrypoint), 0o700); err != nil { t.Fatalf("MkdirAll() error = %v", err) } if err := os.WriteFile(entrypoint, []byte("x"), 0o700); err != nil { t.Fatalf("WriteFile() error = %v", err) } return store, appRoot } func newService(t *testing.T, records InstalledAppResolver, launcher ProcessLauncher) *Service { t.Helper() service, err := NewService(ServiceConfig{ Records: records, Compatibility: compatibilityFunc(func(string) (bool, error) { return true, nil }), Authorization: authorizationFunc(func(string) (bool, error) { return true, nil }), TargetState: targetStateFunc(func(string, string) (bool, error) { return false, nil }), Launcher: launcher, }) if err != nil { t.Fatalf("NewService() error = %v", err) } return service } func launchErrorCode(t *testing.T, err error) FailureCode { t.Helper() var launchErr *Error if !errors.As(err, &launchErr) { t.Fatalf("error = %v, want launch Error", err) } return launchErr.Code } type compatibilityFunc func(string) (bool, error) type resolverFunc func(string) (storage.InstalledApp, string, error) func (resolver resolverFunc) ResolveCurrent(appID string) (storage.InstalledApp, string, error) { return resolver(appID) } func (checker compatibilityFunc) IsCompatible(minOS string) (bool, error) { return checker(minOS) } type authorizationFunc func(string) (bool, error) func (checker authorizationFunc) IsAuthorized(appID string) (bool, error) { return checker(appID) } type targetStateFunc func(string, string) (bool, error) func (checker targetStateFunc) IsRunning(appID, entrypoint string) (bool, error) { return checker(appID, entrypoint) } type recordingLauncher struct { command Command pid int err error calls int } func (launcher *recordingLauncher) Start(command Command) (int, error) { launcher.calls++ launcher.command = command if launcher.err != nil { return 0, launcher.err } return launcher.pid, nil }