Skip to content
Open

Ci test #1821

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b6319c2
updated README
robaweku-bit Aug 6, 2025
4343e53
Add CI workflow for pull requests
robaweku-bit Aug 8, 2025
6090920
Update CI job to print Go version
robaweku-bit Aug 8, 2025
9ffb053
tests: add unit tests for GetAPIKey
robaweku-bit Aug 12, 2025
aa60d0b
Add unit tests for GetAPIKey
robaweku-bit Aug 12, 2025
0af7823
Intentionally break tests for CI check
robaweku-bit Aug 12, 2025
7d16b0f
Fix tests
robaweku-bit Aug 12, 2025
4697aad
Fix tests
robaweku-bit Aug 12, 2025
62b90c5
Intentionally break test to check CI
robaweku-bit Aug 13, 2025
de29021
Run Go tests in CI instead of printing version
robaweku-bit Aug 13, 2025
44c74cd
Break code to test CI failure
robaweku-bit Aug 13, 2025
20b9c6c
Intentionally break test to check CI
robaweku-bit Aug 13, 2025
9b21c73
Intentionally break test to check CI
robaweku-bit Aug 13, 2025
e069c9a
Update CI to run Go tests
robaweku-bit Aug 13, 2025
f7ee241
Temporarily break test to check CI fails
robaweku-bit Aug 13, 2025
eb85e97
Fix test to pass again
robaweku-bit Aug 13, 2025
efd17cb
Temporarily break test to verify CI fails
robaweku-bit Aug 13, 2025
89d87e4
Fix test to pass CI
robaweku-bit Aug 13, 2025
b23c6bc
ci test yml
robaweku-bit Aug 13, 2025
2f00d35
ci test yml
robaweku-bit Aug 13, 2025
24c9657
ci test yml
robaweku-bit Aug 13, 2025
fa6ac33
ci test yml
robaweku-bit Aug 13, 2025
09c577b
Trigger CI with broken test
robaweku-bit Aug 13, 2025
90eb4f0
ci test yml
robaweku-bit Aug 13, 2025
fe5e436
ci test yml
robaweku-bit Aug 13, 2025
3fc0889
ci test yml
robaweku-bit Aug 13, 2025
1fb1cc6
ci test yml
robaweku-bit Aug 13, 2025
05b2b40
ci test yml
robaweku-bit Aug 13, 2025
fdb7061
ci test yml
robaweku-bit Aug 13, 2025
f57c1da
ci test yml
robaweku-bit Aug 13, 2025
de3369e
ci test yml
robaweku-bit Aug 13, 2025
b44d6d2
ci test yml
robaweku-bit Aug 13, 2025
62eaea5
Add coverage reporting to CI tests
robaweku-bit Aug 14, 2025
60671be
Update README.md
robaweku-bit Aug 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: CI

on:
push:
branches:
- main
- ci-test
pull_request:
branches:
- main

jobs:
tests:
name: Tests
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.24

- name: Run tests with coverage
run: |
echo "'Tests' job: go test"
go test -cover ./...
5 changes: 5 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/profiles_settings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/learn-cicd-starter.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[![Tests](https://github.com/robaweku-bit/learn-cicd-starter/actions/workflows/ci.yml/badge.svg)](https://github.com/robaweku-bit/learn-cicd-starter/actions)
# learn-cicd-starter (Notely)

This repo contains the starter code for the "Notely" application for the "Learn CICD" course on [Boot.dev](https://boot.dev).
Expand All @@ -21,3 +22,7 @@ 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!


ev's Notely app."
"robbieMYNAME's version of Boot.dev's Notely app."app."
30 changes: 21 additions & 9 deletions internal/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,30 @@ import (
"strings"
)

var ErrNoAuthHeaderIncluded = errors.New("no authorization header included")
// GetAPIKey extracts an API key from the Authorization header.
// The expected format is: "Authorization: ApiKey <key>"
func GetAPIKey(r *http.Request) (string, error) {
const prefix = "ApiKey "

// GetAPIKey -
func GetAPIKey(headers http.Header) (string, error) {
authHeader := headers.Get("Authorization")
// Get the Authorization header value
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
return "", ErrNoAuthHeaderIncluded
return "", errors.New("authorization header is missing")
}
splitAuth := strings.Split(authHeader, " ")
if len(splitAuth) < 2 || splitAuth[0] != "ApiKey" {
return "", errors.New("malformed authorization header")

// Check for the correct prefix
if !strings.HasPrefix(authHeader, prefix) {
return "", errors.New("authorization header must start with 'ApiKey '")
}

return splitAuth[1], nil
// Trim the prefix to get the actual key
apiKey := strings.TrimPrefix(authHeader, prefix)
apiKey = strings.TrimSpace(apiKey)

if apiKey == "" {
return "", errors.New("API key is missing after prefix")
}

return apiKey, nil
}

82 changes: 82 additions & 0 deletions internal/auth/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package auth

import (
"net/http"
"testing"
)

func TestGetAPIKey(t *testing.T) {
tests := []struct {
name string
authHeader string
wantKey string
wantErrSubstr string
}{
{
name: "missing header",
authHeader: "",
wantErrSubstr: "authorization header is missing",
},
{
name: "wrong prefix",
authHeader: "Bearer abc123",
wantErrSubstr: "must start with 'ApiKey '",
},
{
name: "empty key after prefix",
authHeader: "ApiKey ",
wantErrSubstr: "API key is missing",
},
{
name: "Valid key",
authHeader: "ApiKey my-secret-key",
wantKey: "my-secret-key", //correct
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
req, _ := http.NewRequest("GET", "/", nil)
if tc.authHeader != "" {
req.Header.Set("Authorization", tc.authHeader)
}

gotKey, err := GetAPIKey(req)

if tc.wantErrSubstr != "" {
if err == nil || !contains(err.Error(), tc.wantErrSubstr) {
t.Errorf("expected error containing %q, got %v", tc.wantErrSubstr, err)
}
return
}

if err != nil {
t.Errorf("unexpected error: %v", err)
}
if gotKey != tc.wantKey {
t.Errorf("expected key %q, got %q", tc.wantKey, gotKey)
}
})
}
}

func contains(s, substr string) bool {
return len(substr) == 0 || (len(s) >= len(substr) && stringContains(s, substr))
}

func stringContains(s, substr string) bool {
return len(substr) <= len(s) && (indexOf(s, substr) >= 0)
}

func indexOf(s, substr string) int {
for i := range s {
if len(s)-i < len(substr) {
return -1
}
if s[i:i+len(substr)] == substr {
return i
}
}
return -1
}
// trigger CI
14 changes: 8 additions & 6 deletions middleware_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,24 @@ import (
"github.com/bootdotdev/learn-cicd-starter/internal/database"
)

type authedHandler func(http.ResponseWriter, *http.Request, database.User)

func (cfg *apiConfig) middlewareAuth(handler authedHandler) http.HandlerFunc {
func (cfg *apiConfig) middlewareAuth(
handler func(http.ResponseWriter, *http.Request, database.User),
) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
apiKey, err := auth.GetAPIKey(r.Header)
apiKey, err := auth.GetAPIKey(r)
if err != nil {
respondWithError(w, http.StatusUnauthorized, "Couldn't find api key", err)
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}

// Look up the user in the database
user, err := cfg.DB.GetUser(r.Context(), apiKey)
if err != nil {
respondWithError(w, http.StatusNotFound, "Couldn't get user", err)
http.Error(w, "invalid API key", http.StatusUnauthorized)
return
}

// Call the actual handler with the authenticated user
handler(w, r, user)
}
}