CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl-labs/go-api-testing

Go API testing patterns -- httptest setup, table-driven tests with subtests, test helpers, middleware testing, dependency injection with interfaces, database isolation, parallel tests, testify assertions, golden files

98

1.06x
Quality

98%

Does it follow best practices?

Impact

99%

1.06x

Average score across 5 eval scenarios

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

task.mdevals/scenario-3/

Write Tests for Go Authentication Middleware

Problem/Feature Description

A team building a Go REST API has added JWT-based authentication middleware. The middleware extracts a Bearer token from the Authorization header, validates it, and injects the user email into the request context. Protected endpoints use this context value to identify the current user.

After a recent refactor, the middleware accidentally started accepting expired tokens. The team wants a test suite that catches this class of bug by testing the middleware in isolation (wrapping a dummy handler) and also testing a protected endpoint through the full middleware chain.

Output Specification

Produce the following files:

  • middleware_test.go -- tests for the auth middleware in isolation
  • handlers_test.go -- tests for the protected /api/profile endpoint through the middleware chain
  • testhelpers_test.go -- shared test helpers

The tests must use httptest from the standard library. Table-driven tests with t.Run subtests are expected for the multiple auth scenarios.

Input Files

The following files are provided as inputs. Extract them before beginning.

=============== FILE: go.mod =============== module authapi

go 1.23

require github.com/golang-jwt/jwt/v5 v5.2.1

=============== FILE: main.go =============== package main

import ( "context" "encoding/json" "log" "net/http" "strings" "time"

"github.com/golang-jwt/jwt/v5"

)

var jwtSecret = []byte("test-secret-key-do-not-use-in-production")

type contextKey string

const userEmailKey contextKey = "userEmail"

func main() { mux := setupRoutes() log.Println("Server starting on :8080") log.Fatal(http.ListenAndServe(":8080", mux)) }

func setupRoutes() *http.ServeMux { mux := http.NewServeMux() mux.HandleFunc("GET /api/health", handleHealth) mux.Handle("GET /api/profile", AuthMiddleware(http.HandlerFunc(handleProfile))) mux.HandleFunc("POST /api/login", handleLogin) return mux }

func AuthMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { authHeader := r.Header.Get("Authorization") if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") { writeError(w, http.StatusUnauthorized, "missing or invalid authorization header") return }

tokenString := strings.TrimPrefix(authHeader, "Bearer ")
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
		return jwtSecret, nil
	})
	if err != nil || !token.Valid {
		writeError(w, http.StatusUnauthorized, "invalid token")
		return
	}

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		writeError(w, http.StatusUnauthorized, "invalid token claims")
		return
	}

	email, ok := claims["email"].(string)
	if !ok {
		writeError(w, http.StatusUnauthorized, "missing email in token")
		return
	}

	ctx := context.WithValue(r.Context(), userEmailKey, email)
	next.ServeHTTP(w, r.WithContext(ctx))
})

}

func handleHealth(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]any{"status": "ok"}) }

func handleProfile(w http.ResponseWriter, r *http.Request) { email := r.Context().Value(userEmailKey).(string) writeJSON(w, http.StatusOK, map[string]any{ "data": map[string]any{"email": email, "role": "user"}, }) }

func handleLogin(w http.ResponseWriter, r *http.Request) { var body struct { Email string json:"email" Password string json:"password" } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeError(w, http.StatusBadRequest, "invalid JSON") return } if body.Email == "" || body.Password == "" { writeError(w, http.StatusBadRequest, "email and password required") return } // Simplified: accept any email/password token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "email": body.Email, "exp": time.Now().Add(time.Hour).Unix(), }) tokenString, _ := token.SignedString(jwtSecret) writeJSON(w, http.StatusOK, map[string]any{"access_token": tokenString}) }

func writeJSON(w http.ResponseWriter, status int, data any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(data) }

func writeError(w http.ResponseWriter, status int, message string) { writeJSON(w, status, map[string]any{"error": map[string]any{"message": message}}) }

func generateTestToken(email string, expiry time.Time) string { token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "email": email, "exp": expiry.Unix(), }) s, _ := token.SignedString(jwtSecret) return s }

evals

tile.json