test: Add ensureScope tests for API package
Add comprehensive tests for the ensureScope function covering: - Empty scope parameter (always allows access) - No token in context (session-based request, allows access) - Token with matching scope (allows access) - Token with multiple scopes including required one (allows access) - Token missing required scope (rejects with 403) - Token with empty scopes (defaults to wildcard, allows access) - Rejection returns proper JSON response format Coverage: ensureScope 0% → 100% Coverage: API package 32.1% → 32.2%
This commit is contained in:
parent
301338d800
commit
ef6a370144
1 changed files with 113 additions and 0 deletions
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||||
|
|
@ -119,3 +120,115 @@ func TestGetAPITokenRecordFromRequestNoValue(t *testing.T) {
|
||||||
t.Fatal("expected nil when no token in context")
|
t.Fatal("expected nil when no token in context")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensureScope tests
|
||||||
|
|
||||||
|
func TestEnsureScope_EmptyScopeAllowsAll(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// Empty scope should always return true
|
||||||
|
result := ensureScope(rr, req, "")
|
||||||
|
if !result {
|
||||||
|
t.Fatal("expected ensureScope to return true for empty scope")
|
||||||
|
}
|
||||||
|
if rr.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected no status written (200 default), got %d", rr.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnsureScope_NoTokenAllowsAccess(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// No token attached - should allow access (session-based request)
|
||||||
|
result := ensureScope(rr, req, config.ScopeSettingsWrite)
|
||||||
|
if !result {
|
||||||
|
t.Fatal("expected ensureScope to return true when no token (session request)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnsureScope_TokenWithMatchingScopeAllowsAccess(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
record := config.APITokenRecord{ID: "token-1", Scopes: []string{config.ScopeMonitoringRead}}
|
||||||
|
attachAPITokenRecord(req, &record)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
result := ensureScope(rr, req, config.ScopeMonitoringRead)
|
||||||
|
if !result {
|
||||||
|
t.Fatal("expected ensureScope to return true when token has matching scope")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnsureScope_TokenWithMultipleScopesAllowsAccess(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
record := config.APITokenRecord{
|
||||||
|
ID: "token-multi",
|
||||||
|
Scopes: []string{config.ScopeMonitoringRead, config.ScopeSettingsWrite, config.ScopeDockerReport},
|
||||||
|
}
|
||||||
|
attachAPITokenRecord(req, &record)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
result := ensureScope(rr, req, config.ScopeSettingsWrite)
|
||||||
|
if !result {
|
||||||
|
t.Fatal("expected ensureScope to return true when token has required scope among multiple")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnsureScope_TokenMissingScopeRejects(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
record := config.APITokenRecord{ID: "token-2", Scopes: []string{config.ScopeMonitoringRead}}
|
||||||
|
attachAPITokenRecord(req, &record)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
result := ensureScope(rr, req, config.ScopeSettingsWrite)
|
||||||
|
if result {
|
||||||
|
t.Fatal("expected ensureScope to return false when token missing required scope")
|
||||||
|
}
|
||||||
|
if rr.Code != http.StatusForbidden {
|
||||||
|
t.Fatalf("expected status 403, got %d", rr.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnsureScope_TokenWithEmptyScopesDefaultsToWildcard(t *testing.T) {
|
||||||
|
// Note: Empty scopes defaults to wildcard access via ensureScopes()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
record := config.APITokenRecord{ID: "token-empty", Scopes: []string{}}
|
||||||
|
attachAPITokenRecord(req, &record)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// Empty scopes defaults to ScopeWildcard, so any scope should be allowed
|
||||||
|
result := ensureScope(rr, req, config.ScopeDockerReport)
|
||||||
|
if !result {
|
||||||
|
t.Fatal("expected ensureScope to return true (empty scopes defaults to wildcard)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnsureScope_RejectsWithProperJSONResponse(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
record := config.APITokenRecord{ID: "token-3", Scopes: []string{"other:scope"}}
|
||||||
|
attachAPITokenRecord(req, &record)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
result := ensureScope(rr, req, config.ScopeSettingsWrite)
|
||||||
|
if result {
|
||||||
|
t.Fatal("expected ensureScope to return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check response format
|
||||||
|
if ct := rr.Header().Get("Content-Type"); ct != "application/json" {
|
||||||
|
t.Fatalf("expected Content-Type application/json, got %s", ct)
|
||||||
|
}
|
||||||
|
|
||||||
|
body := rr.Body.String()
|
||||||
|
if body == "" {
|
||||||
|
t.Fatal("expected JSON body in response")
|
||||||
|
}
|
||||||
|
// Body should contain error and requiredScope
|
||||||
|
if !strings.Contains(body, "missing_scope") {
|
||||||
|
t.Fatalf("expected 'missing_scope' in body, got: %s", body)
|
||||||
|
}
|
||||||
|
if !strings.Contains(body, config.ScopeSettingsWrite) {
|
||||||
|
t.Fatalf("expected required scope in body, got: %s", body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue