diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4741c631d3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: ci_ContinuousIntegration + +on: + pull_request: + branches: [main] + +jobs: + tests: + name: Tests + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.25.1" + + - name: Print Go version and exit successfully. + run: go version + + - name: Run Auth test cases + run: go test ./... -cover \ No newline at end of file diff --git a/README.md b/README.md index c2bec0368b7..d6f8092155f 100644 --- a/README.md +++ b/README.md @@ -21,3 +21,8 @@ go build -o notely && ./notely *This starts the server in non-database mode.* It will serve a simple webpage at `http://localhost:8080`. You do *not* need to set up a database or any interactivity on the webpage yet. Instructions for that will come later in the course! + +# Rajendra's vesion of boot.dev's notely application. +# Code coverage badge +![Unit Test Code Coverage](https://github.com/rpm-sconex/bootdevz-learn-cicd-starter/actions/workflows/ci.yml/badge.svg) + diff --git a/internal/auth/auth_test.go b/internal/auth/auth_test.go new file mode 100644 index 00000000000..e567fb59acf --- /dev/null +++ b/internal/auth/auth_test.go @@ -0,0 +1,98 @@ +package auth + +import ( + "errors" + "net/http" + "testing" +) + +func TestGetAPIKey(t *testing.T) { + // Define a slice of test structures with various input scenarios + tests := []struct { + name string // Name of the test case + headers http.Header // Input HTTP headers + expectedAPIKey string // Expected API Key on success + expectedError error // Expected error on failure + }{ + { + name: "Success - Correct ApiKey header", + headers: http.Header{ + "Authorization": []string{"ApiKey my-secret-api-key"}, + }, + expectedAPIKey: "my-secret-api-key", + expectedError: nil, + }, + { + name: "Failure - No Authorization header", + headers: http.Header{}, // Empty headers + expectedAPIKey: "", + expectedError: ErrNoAuthHeaderIncluded, // Matches the specific error var + }, + { + name: "Failure - Header present but empty value", + headers: http.Header{ + "Authorization": []string{""}, + }, + expectedAPIKey: "", + // This case triggers the malformed error due to the Split logic + expectedError: errors.New("malformed authorization header"), + }, + { + name: "Failure - Malformed header - missing space/key part", + headers: http.Header{ + "Authorization": []string{"ApiKey"}, + }, + expectedAPIKey: "", + expectedError: errors.New("malformed authorization header"), + }, + { + name: "Failure - Malformed header - only value present", + headers: http.Header{ + "Authorization": []string{"only-a-key-no-prefix"}, + }, + expectedAPIKey: "", + expectedError: errors.New("malformed authorization header"), + }, + { + name: "Failure - Wrong authorization scheme (e.g., Bearer)", + headers: http.Header{ + "Authorization": []string{"Bearer some-jwt-token"}, + }, + expectedAPIKey: "", + expectedError: errors.New("malformed authorization header"), + }, + { + name: "Failure - Extra spaces in header value (library handles trim implicitly)", + headers: http.Header{ + "Authorization": []string{" ApiKey my-key-with-spaces "}, + }, + // Note: http.Header.Get() trims leading/trailing whitespace automatically. + // The Split function then handles internal spacing appropriately for this structure. + expectedAPIKey: "my-key-with-spaces", + expectedError: nil, + }, + } + + // Iterate through all defined test cases + for _, tt := range tests { + // Run each case as a subtest + t.Run(tt.name, func(t *testing.T) { + gotAPIKey, gotError := GetAPIKey(tt.headers) + + // Check for API Key match + if gotAPIKey != tt.expectedAPIKey { + t.Errorf("GetAPIKey() gotAPIKey = %v, want %v", gotAPIKey, tt.expectedAPIKey) + } + + // Check for error match + // Using Error() string comparison is generally acceptable for expected static errors in simple functions, + // but for specific known errors like ErrNoAuthHeaderIncluded, an `errors.Is` check is better practice. + if (gotError != nil && tt.expectedError != nil) && gotError.Error() != tt.expectedError.Error() { + t.Errorf("GetAPIKey() gotError = %v, want %v", gotError, tt.expectedError) + } else if (gotError != nil) != (tt.expectedError != nil) { + // Checks if one is nil and the other is not + t.Errorf("GetAPIKey() gotError presence mismatch: got %v, want %v", gotError, tt.expectedError) + } + }) + } +}