mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 15:51:26 +00:00
- Stable machine-readable error codes alongside the error message (UNAUTHENTICATED, INVALID_TOKEN, ADMIN_REQUIRED, SCOPE_INSUFFICIENT, IDEMPOTENCY_IN_PROGRESS); CLI renders "CODE: message". - Idempotency middleware on authed/admin groups: POST requests with an Idempotency-Key header replay the first response for 24h (Redis-backed, caller-scoped); concurrent duplicates get 409. X-Idempotent-Replay header marks replays. - Shared pagination helpers: ?cursor= (opaque, preferred) alongside legacy ?page= on projects and deployments lists; pagination meta includes next_cursor; invalid cursors fail soft to page 1. - CLI --idempotency-key flag for safe scripted retries. - OpenAPI updated: error envelope, cursor params, idempotency contract. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
56 lines
1.5 KiB
Go
56 lines
1.5 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
func ctxWithQuery(q string) *gin.Context {
|
|
gin.SetMode(gin.TestMode)
|
|
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
|
c.Request = httptest.NewRequest("GET", "/x?"+q, nil)
|
|
return c
|
|
}
|
|
|
|
func TestPageWindowDefaults(t *testing.T) {
|
|
offset, limit := pageWindow(ctxWithQuery(""), 10, 100)
|
|
if offset != 0 || limit != 10 {
|
|
t.Fatalf("expected 0/10, got %d/%d", offset, limit)
|
|
}
|
|
}
|
|
|
|
func TestPageWindowLegacyPage(t *testing.T) {
|
|
offset, limit := pageWindow(ctxWithQuery("page=3&limit=20"), 10, 100)
|
|
if offset != 40 || limit != 20 {
|
|
t.Fatalf("expected 40/20, got %d/%d", offset, limit)
|
|
}
|
|
}
|
|
|
|
func TestPageWindowCursor(t *testing.T) {
|
|
// cursor for offset 50 produced by paginationMeta
|
|
meta := paginationMeta(0, 50, 200)
|
|
cursor := meta["next_cursor"].(string)
|
|
offset, limit := pageWindow(ctxWithQuery("cursor="+cursor+"&limit=50"), 10, 100)
|
|
if offset != 50 || limit != 50 {
|
|
t.Fatalf("expected 50/50, got %d/%d", offset, limit)
|
|
}
|
|
}
|
|
|
|
func TestPageWindowInvalidCursorFailsSoft(t *testing.T) {
|
|
offset, _ := pageWindow(ctxWithQuery("cursor=garbage"), 10, 100)
|
|
if offset != 0 {
|
|
t.Fatalf("expected offset 0 on invalid cursor, got %d", offset)
|
|
}
|
|
}
|
|
|
|
func TestPaginationMetaNextCursorOnlyWhenMore(t *testing.T) {
|
|
if _, ok := paginationMeta(0, 10, 10)["next_cursor"]; ok {
|
|
t.Fatal("no next page should omit next_cursor")
|
|
}
|
|
if _, ok := paginationMeta(0, 10, 25)["next_cursor"]; !ok {
|
|
t.Fatal("expected next_cursor when more pages exist")
|
|
}
|
|
}
|