This commit is contained in:
1 parent
1e7d759a19
commit
2d6246e14c
23 files changed
+629
-632
No files matched your search
@@ -97,7 +97,7 @@ func NewFiberApp(proc *processor.Processor, svc *service.Service, opts Options)
|
||||
app.Get("/health", h.Health)
|
||||
|
||||
// API v1 routes
|
||||
api := app.Group("/api/v1")
|
||||
api := app.Group("/api")
|
||||
|
||||
// Auth routes with specific rate limiting
|
||||
auth := api.Group("/auth")
|
||||
|
||||
@@ -3,8 +3,11 @@ package http
|
||||
import (
|
||||
"io"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"chess/internal/server/service"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
@@ -59,3 +62,44 @@ func TestIsValidUUIDRequiresCanonicalForm(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIRoutesAreUnversioned(t *testing.T) {
|
||||
svc, err := service.New(nil, []byte("test-secret-test-secret-test-secret"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Route registration only; no handler runs, so no processor is needed.
|
||||
app := NewFiberApp(nil, svc, Options{DevMode: true})
|
||||
|
||||
registered := make(map[string]bool)
|
||||
for _, route := range app.GetRoutes(true) {
|
||||
registered[route.Method+" "+route.Path] = true
|
||||
if strings.HasPrefix(route.Path, "/api/v1") {
|
||||
t.Errorf("versioned route still registered: %s %s", route.Method, route.Path)
|
||||
}
|
||||
}
|
||||
for _, want := range []string{
|
||||
"GET /health",
|
||||
"POST /api/auth/register",
|
||||
"POST /api/auth/login",
|
||||
"GET /api/auth/me",
|
||||
"POST /api/auth/logout",
|
||||
"POST /api/games",
|
||||
"GET /api/games/:gameId",
|
||||
"GET /api/games/:gameId/history",
|
||||
"POST /api/games/:gameId/moves",
|
||||
"GET /api/users/me/games",
|
||||
} {
|
||||
if !registered[want] {
|
||||
t.Errorf("route %s is not registered", want)
|
||||
}
|
||||
}
|
||||
|
||||
response, err := app.Test(httptest.NewRequest("GET", "/api/v1/games/5579b47e-4d3b-4eb3-abbd-846fe94cb955", nil))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if response.StatusCode != fiber.StatusNotFound {
|
||||
t.Fatalf("GET /api/v1/... status = %d, want 404", response.StatusCode)
|
||||
}
|
||||
}
|
||||
@@ -196,7 +196,7 @@ func (s *Service) GenerateGameID() string {
|
||||
|
||||
// ApplyMoveWithState verifies that the position validated by the processor is
|
||||
// still current, then commits the move, optional first-move claim, and result as
|
||||
// one in-memory transition and one SQLite transaction.
|
||||
// one in-memory transition and one database transaction.
|
||||
func (s *Service) ApplyMoveWithState(gameID string, commit MoveCommit) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
@@ -92,7 +92,7 @@ function restoreAuthSession() {
|
||||
|
||||
async function validateSession() {
|
||||
try {
|
||||
const response = await authFetch(`${gameState.apiUrl}/api/v1/auth/me`);
|
||||
const response = await authFetch(`${gameState.apiUrl}/api/auth/me`);
|
||||
if (response.ok) {
|
||||
const user = await response.json();
|
||||
gameState.userId = user.userId;
|
||||
@@ -228,7 +228,7 @@ async function handleLogin() {
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(`${gameState.apiUrl}/api/v1/auth/login`, {
|
||||
response = await fetch(`${gameState.apiUrl}/api/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ identifier, password })
|
||||
@@ -295,7 +295,7 @@ async function handleRegister() {
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(`${gameState.apiUrl}/api/v1/auth/register`, {
|
||||
response = await fetch(`${gameState.apiUrl}/api/auth/register`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
@@ -337,7 +337,7 @@ async function handleRegister() {
|
||||
async function handleLogout() {
|
||||
if (gameState.authToken) {
|
||||
try {
|
||||
await authFetch(`${gameState.apiUrl}/api/v1/auth/logout`, { method: 'POST' });
|
||||
await authFetch(`${gameState.apiUrl}/api/auth/logout`, { method: 'POST' });
|
||||
} catch {
|
||||
// Ignore - clear local state regardless
|
||||
}
|
||||
@@ -619,7 +619,7 @@ async function startNewGame() {
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await authFetch(`${gameState.apiUrl}/api/v1/games`, {
|
||||
response = await authFetch(`${gameState.apiUrl}/api/games`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(requestBody)
|
||||
@@ -771,7 +771,7 @@ async function handleHumanMove(from, to) {
|
||||
const toEl = document.querySelector(`[data-square="${to}"]`);
|
||||
|
||||
try {
|
||||
const response = await authFetch(`${gameState.apiUrl}/api/v1/games/${gameState.gameId}/moves`, {
|
||||
const response = await authFetch(`${gameState.apiUrl}/api/games/${gameState.gameId}/moves`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ move })
|
||||
@@ -808,7 +808,7 @@ async function handleHumanMove(from, to) {
|
||||
async function triggerComputerMove() {
|
||||
lockBoard();
|
||||
try {
|
||||
const response = await authFetch(`${gameState.apiUrl}/api/v1/games/${gameState.gameId}/moves`, {
|
||||
const response = await authFetch(`${gameState.apiUrl}/api/games/${gameState.gameId}/moves`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ move: 'cccc' })
|
||||
@@ -849,7 +849,7 @@ async function pollOnce() {
|
||||
|
||||
try {
|
||||
const response = await authFetch(
|
||||
`${gameState.apiUrl}/api/v1/games/${gameState.gameId}?wait=true&moveCount=${moveCount}`,
|
||||
`${gameState.apiUrl}/api/games/${gameState.gameId}?wait=true&moveCount=${moveCount}`,
|
||||
{ signal: gameState.pollController.signal }
|
||||
);
|
||||
|
||||
@@ -908,7 +908,7 @@ async function undoMoves() {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await authFetch(`${gameState.apiUrl}/api/v1/games/${gameState.gameId}/undo`, {
|
||||
const response = await authFetch(`${gameState.apiUrl}/api/games/${gameState.gameId}/undo`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ count: 2 })
|
||||
|
||||
Reference in new issue
Block a user