diff --git a/checks/cli.go b/checks/cli.go index 58e5e5a..c16e6db 100644 --- a/checks/cli.go +++ b/checks/cli.go @@ -58,10 +58,6 @@ func (b *boundedBuffer) String() string { return b.buffer.String() } -func (b *boundedBuffer) Truncated() bool { - return b.truncated -} - func runCLICommand(command api.CLIStepCLICommand, variables map[string]string) (result api.CLICommandResult) { return runCLICommandWithLimits(command, variables, cliCommandTimeout, maxCLIOutputBytesPerStream) } diff --git a/checks/cli_test.go b/checks/cli_test.go index d85c906..f9923e1 100644 --- a/checks/cli_test.go +++ b/checks/cli_test.go @@ -74,30 +74,6 @@ func TestRunCLICommandCapsOutput(t *testing.T) { } } -func TestBoundedBufferDiscardsExcessBytes(t *testing.T) { - truncations := 0 - buffer := newBoundedBuffer(5, func() { - truncations++ - }) - written, err := buffer.Write([]byte("abcdefgh")) - if err != nil { - t.Fatalf("Write() error = %v", err) - } - if written != 8 { - t.Fatalf("Write() = %d, want 8", written) - } - if got := buffer.String(); got != "abcde" { - t.Fatalf("buffer = %q, want abcde", got) - } - if !buffer.Truncated() { - t.Fatal("buffer did not record truncation") - } - _, _ = buffer.Write([]byte("more")) - if truncations != 1 { - t.Fatalf("truncation callback invoked %d times, want once", truncations) - } -} - func TestRunCLICommandCapturesStdoutVariables(t *testing.T) { variables := map[string]string{} result := runCLICommand(api.CLIStepCLICommand{ @@ -178,21 +154,6 @@ func TestRunCLICommandInterpolatesCapturedStdoutVariables(t *testing.T) { } } -func TestParseStdoutVariablesRequiresOneCaptureGroup(t *testing.T) { - variables := map[string]string{} - err := parseStdoutVariables("token=abc123", []api.CLICommandStdoutVariable{{ - Name: "token", - Regex: `token=([a-z]+)([0-9]+)`, - }}, variables) - - if err == nil { - t.Fatal("expected parse error") - } - if err.Error() != "invalid stdout variable configuration" { - t.Fatalf("error = %q, want invalid stdout variable configuration", err.Error()) - } -} - func TestParseStdoutVariablesUsesGenericConfigurationError(t *testing.T) { tests := []struct { name string diff --git a/checks/http_test.go b/checks/http_test.go index 1a58e19..3e8fa11 100644 --- a/checks/http_test.go +++ b/checks/http_test.go @@ -7,7 +7,6 @@ import ( "net/http/httptest" "strings" "testing" - "time" api "github.com/bootdotdev/bootdev/client" ) @@ -240,39 +239,6 @@ func TestRunHTTPRequestCapsResponseBodyRead(t *testing.T) { } } -func TestRunHTTPRequestHonorsClientTimeout(t *testing.T) { - const timeout = 20 * time.Millisecond - client := &http.Client{ - Timeout: timeout, - Transport: httpRoundTripFunc(func(r *http.Request) (*http.Response, error) { - <-r.Context().Done() - return nil, r.Context().Err() - }), - } - requestStep := api.CLIStepHTTPRequest{ - Request: api.HTTPRequest{ - Method: http.MethodGet, - FullURL: "http://example.test", - }, - } - - start := time.Now() - result := runHTTPRequest(client, "", map[string]string{}, requestStep) - elapsed := time.Since(start) - if result.Err == "" { - t.Fatal("runHTTPRequest() unexpectedly succeeded") - } - if elapsed > time.Second { - t.Fatalf("runHTTPRequest() took %v, want a prompt timeout", elapsed) - } -} - -func TestLessonHTTPClientUsesConfiguredTimeout(t *testing.T) { - if got := newLessonHTTPClient().Timeout; got != lessonHTTPRequestTimeout { - t.Fatalf("lesson HTTP client timeout = %v, want %v", got, lessonHTTPRequestTimeout) - } -} - func TestRunHTTPRequestCapturesResponseHeaderVariableAndDoesNotFollowRedirect(t *testing.T) { followRedirects := false diff --git a/checks/jq_test.go b/checks/jq_test.go index c5f838f..f86667b 100644 --- a/checks/jq_test.go +++ b/checks/jq_test.go @@ -2,7 +2,6 @@ package checks import ( "reflect" - "strings" "testing" api "github.com/bootdotdev/bootdev/client" @@ -15,7 +14,7 @@ func TestRunStdoutJqQuery(t *testing.T) { test api.StdoutJqTest variables map[string]string want api.CLICommandJqOutput - wantError string + wantError bool }{ { name: "queries json with interpolated query", @@ -52,7 +51,7 @@ func TestRunStdoutJqQuery(t *testing.T) { want: api.CLICommandJqOutput{ Query: `.name`, }, - wantError: "invalid character", + wantError: true, }, { name: "returns jq error", @@ -63,20 +62,20 @@ func TestRunStdoutJqQuery(t *testing.T) { }, want: api.CLICommandJqOutput{ Query: `.name[`, - Error: "unexpected EOF", }, + wantError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := runStdoutJqQuery(tt.stdout, tt.test, tt.variables) - if tt.wantError != "" { + if tt.wantError { if got.Query != tt.want.Query { t.Fatalf("Query = %q, want %q", got.Query, tt.want.Query) } - if !strings.Contains(got.Error, tt.wantError) { - t.Fatalf("expected error containing %q, got %q", tt.wantError, got.Error) + if got.Error == "" { + t.Fatal("expected an error") } return } @@ -97,34 +96,6 @@ func TestParseJqInputRejectsMultipleJSONValuesInJSONMode(t *testing.T) { } } -func TestFormatJqResults(t *testing.T) { - got := formatJqResults([]any{"hello", float64(42), true, nil, map[string]any{"id": float64(1)}}) - want := []string{`"hello"`, `42`, `true`, `null`, `{"id":1}`} - if !reflect.DeepEqual(got, want) { - t.Fatalf("formatJqResults() = %#v, want %#v", got, want) - } -} - -func TestFormatJqExpectedValueInterpolatesOnlyStrings(t *testing.T) { - variables := map[string]string{"name": "Allan"} - - gotString := formatJqExpectedValue(api.JqExpectedResult{ - Type: api.JqTypeString, - Value: "hello ${name}", - }, variables) - if gotString != `"hello Allan"` { - t.Fatalf("expected interpolated string value, got %q", gotString) - } - - gotInt := formatJqExpectedValue(api.JqExpectedResult{ - Type: api.JqTypeInt, - Value: "${name}", - }, variables) - if gotInt != `"${name}"` { - t.Fatalf("expected non-string jq type to avoid interpolation, got %q", gotInt) - } -} - func TestValFromJqPath(t *testing.T) { tests := []struct { name string diff --git a/checks/local_test.go b/checks/local_test.go index 82fa6cd..a679905 100644 --- a/checks/local_test.go +++ b/checks/local_test.go @@ -73,28 +73,19 @@ func TestLocalSubmissionEventReportsFirstFailure(t *testing.T) { } } -func TestEvaluateCLICommandReportsStdoutVariableParseError(t *testing.T) { - cliData := api.CLIData{Steps: []api.CLIStep{ - {CLICommand: &api.CLIStepCLICommand{Tests: []api.CLICommandTest{ - {ExitCode: intPtr(0)}, - }}}, - }} - results := []api.CLIStepResult{ - {CLICommandResult: &api.CLICommandResult{ - ExitCode: 0, - Err: "invalid stdout variable configuration", - }}, - } +func TestEvaluateCLICommandReportsExecutionError(t *testing.T) { + const message = "invalid stdout variable configuration" + failure := evaluateCLICommandTests( + 0, + api.CLIStepCLICommand{}, + api.CLICommandResult{Err: message}, + ) - event := LocalSubmissionEvent(cliData, results) - if event.ResultSlug != api.VerificationResultSlugFailure { - t.Fatalf("ResultSlug = %q, want failure", event.ResultSlug) - } - if event.StructuredErrCLI == nil { + if failure == nil { t.Fatal("expected structured failure") } - if event.StructuredErrCLI.ErrorMessage != "invalid stdout variable configuration" { - t.Fatalf("ErrorMessage = %q, want stdout variable error", event.StructuredErrCLI.ErrorMessage) + if failure.ErrorMessage != message { + t.Fatalf("ErrorMessage = %q, want %q", failure.ErrorMessage, message) } } diff --git a/checks/runner_test.go b/checks/runner_test.go index 57f027e..62885c0 100644 --- a/checks/runner_test.go +++ b/checks/runner_test.go @@ -3,6 +3,7 @@ package checks import ( "net/http" "net/http/httptest" + "reflect" "testing" api "github.com/bootdotdev/bootdev/client" @@ -155,10 +156,8 @@ func TestApplySubmissionResultsStopsAfterFailedHTTPTest(t *testing.T) { func applySubmissionResultsMessages(cliData api.CLIData, failure *api.StructuredErrCLI) []tea.Msg { ch := make(chan tea.Msg) - done := make(chan struct{}) go func() { defer close(ch) - defer close(done) ApplySubmissionResults(cliData, failure, ch) }() @@ -166,53 +165,17 @@ func applySubmissionResultsMessages(cliData api.CLIData, failure *api.Structured for msg := range ch { msgs = append(msgs, msg) } - <-done return msgs } func assertMessages(t *testing.T, got []tea.Msg, want []tea.Msg) { t.Helper() - if len(got) != len(want) { - t.Fatalf("got %d messages, want %d\ngot: %#v\nwant: %#v", len(got), len(want), got, want) - } - for i := range want { - assertMessage(t, i, got[i], want[i]) - } -} - -func assertMessage(t *testing.T, index int, got tea.Msg, want tea.Msg) { - t.Helper() - - switch want := want.(type) { - case messages.ResolveStepMsg: - got, ok := got.(messages.ResolveStepMsg) - if !ok { - t.Fatalf("message %d = %T, want %T", index, got, want) - } - if got.Index != want.Index || !sameBoolPtr(got.Passed, want.Passed) { - t.Fatalf("message %d = %#v, want %#v", index, got, want) - } - case messages.ResolveTestMsg: - got, ok := got.(messages.ResolveTestMsg) - if !ok { - t.Fatalf("message %d = %T, want %T", index, got, want) - } - if got.StepIndex != want.StepIndex || got.TestIndex != want.TestIndex || !sameBoolPtr(got.Passed, want.Passed) { - t.Fatalf("message %d = %#v, want %#v", index, got, want) - } - default: - t.Fatalf("unsupported wanted message type %T", want) + if !reflect.DeepEqual(got, want) { + t.Fatalf("messages = %#v, want %#v", got, want) } } func boolPtr(v bool) *bool { return &v } - -func sameBoolPtr(a *bool, b *bool) bool { - if a == nil || b == nil { - return a == b - } - return *a == *b -} diff --git a/client/auth_test.go b/client/auth_test.go deleted file mode 100644 index 4c5d7ce..0000000 --- a/client/auth_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package api - -import ( - "errors" - "net" - "net/http" - "testing" - "time" - - "github.com/spf13/viper" -) - -type roundTripFunc func(*http.Request) (*http.Response, error) - -func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) { - return f(r) -} - -func TestFetchAccessTokenHasOverallTimeout(t *testing.T) { - originalClient := apiHTTPClient - originalAPIURL := viper.GetString("api_url") - originalRefreshToken := viper.GetString("refresh_token") - t.Cleanup(func() { - apiHTTPClient = originalClient - viper.Set("api_url", originalAPIURL) - viper.Set("refresh_token", originalRefreshToken) - }) - - const timeout = 20 * time.Millisecond - apiHTTPClient = &http.Client{ - Timeout: timeout, - Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) { - <-r.Context().Done() - return nil, r.Context().Err() - }), - } - viper.Set("api_url", "http://api.example") - viper.Set("refresh_token", "refresh-token") - - start := time.Now() - _, err := FetchAccessToken() - elapsed := time.Since(start) - if err == nil { - t.Fatal("FetchAccessToken() unexpectedly succeeded") - } - - var netErr net.Error - if !errors.As(err, &netErr) || !netErr.Timeout() { - t.Fatalf("FetchAccessToken() error = %v, want timeout error", err) - } - if elapsed > time.Second { - t.Fatalf("FetchAccessToken() took %v, want a prompt timeout", elapsed) - } -} - -func TestAPIHTTPClientUsesConfiguredTimeout(t *testing.T) { - if apiHTTPClient.Timeout != apiRequestTimeout { - t.Fatalf("API client timeout = %v, want %v", apiHTTPClient.Timeout, apiRequestTimeout) - } -} diff --git a/cmd/logout_test.go b/cmd/logout_test.go index 20bd45e..29ecd3c 100644 --- a/cmd/logout_test.go +++ b/cmd/logout_test.go @@ -79,9 +79,3 @@ func TestLogoutReturnsConfigWriteError(t *testing.T) { t.Fatalf("logout() error = %q, want config write error", err) } } - -func TestLogoutCommandDoesNotRequireAuthentication(t *testing.T) { - if logoutCmd.PreRun != nil { - t.Fatal("logout command unexpectedly requires authentication") - } -} diff --git a/render/variables_test.go b/render/variables_test.go index f95c12f..55abbf5 100644 --- a/render/variables_test.go +++ b/render/variables_test.go @@ -35,13 +35,7 @@ func TestHTTPVariableSections(t *testing.T) { }, } - got := renderVariableSection("Variables Saved", savedVariablesForHTTPResult(result)) - got += renderVariableSection("Variables Missing", missingSaveVariablesForHTTPResult(result)) - available, expectsVariables := availableVariablesForHTTPResult(result) - if !expectsVariables { - t.Fatalf("expected HTTP request to use variables") - } - got += renderVariableSection("Variables Available", available) + got := printHTTPRequestResult(result) wantContains := []string{ "Variables Saved:", diff --git a/version/version_test.go b/version/version_test.go index 316cf2a..3a8d69a 100644 --- a/version/version_test.go +++ b/version/version_test.go @@ -10,37 +10,6 @@ import ( "time" ) -func TestGetLatestVersionRespectsGoProxyOff(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("test uses a POSIX executable script") - } - - dir := t.TempDir() - fakeGo := filepath.Join(dir, "go") - script := `#!/bin/sh -if [ "$GOPROXY" != "off" ]; then - echo "unexpected GOPROXY: $GOPROXY" >&2 - exit 1 -fi -printf '{"Version":"v1.2.3"}' -` - if err := os.WriteFile(fakeGo, []byte(script), 0o755); err != nil { - t.Fatalf("create fake go command: %v", err) - } - t.Setenv("PATH", dir) - t.Setenv("GOPROXY", "off") - t.Setenv("GOPRIVATE", "") - t.Setenv("GONOPROXY", "none") - - latest, err := getLatestVersionWithTimeout(time.Second) - if err != nil { - t.Fatalf("get latest version: %v", err) - } - if latest != "v1.2.3" { - t.Fatalf("latest version = %q, want v1.2.3", latest) - } -} - func TestGetLatestVersionHasOverallTimeout(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("test uses a POSIX executable script")