mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-02-16 21:36:07 +00:00
A lot more tests
This commit is contained in:
181
pkg/restheadspec/context_test.go
Normal file
181
pkg/restheadspec/context_test.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package restheadspec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/bitechdev/ResolveSpec/pkg/common"
|
||||
)
|
||||
|
||||
func TestContextOperations(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Test Schema
|
||||
t.Run("WithSchema and GetSchema", func(t *testing.T) {
|
||||
ctx = WithSchema(ctx, "public")
|
||||
schema := GetSchema(ctx)
|
||||
if schema != "public" {
|
||||
t.Errorf("Expected schema 'public', got '%s'", schema)
|
||||
}
|
||||
})
|
||||
|
||||
// Test Entity
|
||||
t.Run("WithEntity and GetEntity", func(t *testing.T) {
|
||||
ctx = WithEntity(ctx, "users")
|
||||
entity := GetEntity(ctx)
|
||||
if entity != "users" {
|
||||
t.Errorf("Expected entity 'users', got '%s'", entity)
|
||||
}
|
||||
})
|
||||
|
||||
// Test TableName
|
||||
t.Run("WithTableName and GetTableName", func(t *testing.T) {
|
||||
ctx = WithTableName(ctx, "public.users")
|
||||
tableName := GetTableName(ctx)
|
||||
if tableName != "public.users" {
|
||||
t.Errorf("Expected tableName 'public.users', got '%s'", tableName)
|
||||
}
|
||||
})
|
||||
|
||||
// Test Model
|
||||
t.Run("WithModel and GetModel", func(t *testing.T) {
|
||||
type TestModel struct {
|
||||
ID int
|
||||
Name string
|
||||
}
|
||||
model := &TestModel{ID: 1, Name: "test"}
|
||||
ctx = WithModel(ctx, model)
|
||||
retrieved := GetModel(ctx)
|
||||
if retrieved == nil {
|
||||
t.Error("Expected model to be retrieved, got nil")
|
||||
}
|
||||
if retrievedModel, ok := retrieved.(*TestModel); ok {
|
||||
if retrievedModel.ID != 1 || retrievedModel.Name != "test" {
|
||||
t.Errorf("Expected model with ID=1 and Name='test', got ID=%d, Name='%s'", retrievedModel.ID, retrievedModel.Name)
|
||||
}
|
||||
} else {
|
||||
t.Error("Retrieved model is not of expected type")
|
||||
}
|
||||
})
|
||||
|
||||
// Test ModelPtr
|
||||
t.Run("WithModelPtr and GetModelPtr", func(t *testing.T) {
|
||||
type TestModel struct {
|
||||
ID int
|
||||
}
|
||||
models := []*TestModel{}
|
||||
ctx = WithModelPtr(ctx, &models)
|
||||
retrieved := GetModelPtr(ctx)
|
||||
if retrieved == nil {
|
||||
t.Error("Expected modelPtr to be retrieved, got nil")
|
||||
}
|
||||
})
|
||||
|
||||
// Test Options
|
||||
t.Run("WithOptions and GetOptions", func(t *testing.T) {
|
||||
limit := 10
|
||||
options := ExtendedRequestOptions{
|
||||
RequestOptions: common.RequestOptions{
|
||||
Limit: &limit,
|
||||
},
|
||||
}
|
||||
ctx = WithOptions(ctx, options)
|
||||
retrieved := GetOptions(ctx)
|
||||
if retrieved == nil {
|
||||
t.Error("Expected options to be retrieved, got nil")
|
||||
return
|
||||
}
|
||||
if retrieved.Limit == nil || *retrieved.Limit != 10 {
|
||||
t.Error("Expected options to be retrieved with limit=10")
|
||||
}
|
||||
})
|
||||
|
||||
// Test WithRequestData
|
||||
t.Run("WithRequestData", func(t *testing.T) {
|
||||
type TestModel struct {
|
||||
ID int
|
||||
Name string
|
||||
}
|
||||
model := &TestModel{ID: 1, Name: "test"}
|
||||
modelPtr := &[]*TestModel{}
|
||||
limit := 20
|
||||
options := ExtendedRequestOptions{
|
||||
RequestOptions: common.RequestOptions{
|
||||
Limit: &limit,
|
||||
},
|
||||
}
|
||||
|
||||
ctx = WithRequestData(ctx, "test_schema", "test_entity", "test_schema.test_entity", model, modelPtr, options)
|
||||
|
||||
if GetSchema(ctx) != "test_schema" {
|
||||
t.Errorf("Expected schema 'test_schema', got '%s'", GetSchema(ctx))
|
||||
}
|
||||
if GetEntity(ctx) != "test_entity" {
|
||||
t.Errorf("Expected entity 'test_entity', got '%s'", GetEntity(ctx))
|
||||
}
|
||||
if GetTableName(ctx) != "test_schema.test_entity" {
|
||||
t.Errorf("Expected tableName 'test_schema.test_entity', got '%s'", GetTableName(ctx))
|
||||
}
|
||||
if GetModel(ctx) == nil {
|
||||
t.Error("Expected model to be set")
|
||||
}
|
||||
if GetModelPtr(ctx) == nil {
|
||||
t.Error("Expected modelPtr to be set")
|
||||
}
|
||||
opts := GetOptions(ctx)
|
||||
if opts == nil {
|
||||
t.Error("Expected options to be set")
|
||||
return
|
||||
}
|
||||
if opts.Limit == nil || *opts.Limit != 20 {
|
||||
t.Error("Expected options to be set with limit=20")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestEmptyContext(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("GetSchema with empty context", func(t *testing.T) {
|
||||
schema := GetSchema(ctx)
|
||||
if schema != "" {
|
||||
t.Errorf("Expected empty schema, got '%s'", schema)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetEntity with empty context", func(t *testing.T) {
|
||||
entity := GetEntity(ctx)
|
||||
if entity != "" {
|
||||
t.Errorf("Expected empty entity, got '%s'", entity)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetTableName with empty context", func(t *testing.T) {
|
||||
tableName := GetTableName(ctx)
|
||||
if tableName != "" {
|
||||
t.Errorf("Expected empty tableName, got '%s'", tableName)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetModel with empty context", func(t *testing.T) {
|
||||
model := GetModel(ctx)
|
||||
if model != nil {
|
||||
t.Errorf("Expected nil model, got %v", model)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetModelPtr with empty context", func(t *testing.T) {
|
||||
modelPtr := GetModelPtr(ctx)
|
||||
if modelPtr != nil {
|
||||
t.Errorf("Expected nil modelPtr, got %v", modelPtr)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetOptions with empty context", func(t *testing.T) {
|
||||
options := GetOptions(ctx)
|
||||
// GetOptions returns nil when context is empty
|
||||
if options != nil {
|
||||
t.Errorf("Expected nil options in empty context, got %v", options)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -226,8 +226,20 @@ func (h *Handler) HandleGet(w common.ResponseWriter, r common.Request, params ma
|
||||
return
|
||||
}
|
||||
|
||||
metadata := h.generateMetadata(schema, entity, model)
|
||||
h.sendResponse(w, metadata, nil)
|
||||
// Parse request options from headers to get response format settings
|
||||
options := h.parseOptionsFromHeaders(r, model)
|
||||
|
||||
tableMetadata := h.generateMetadata(schema, entity, model)
|
||||
// Send with formatted response to respect DetailApi/SimpleApi/Syncfusion format
|
||||
// Create empty metadata for response wrapper
|
||||
responseMetadata := &common.Metadata{
|
||||
Total: 0,
|
||||
Filtered: 0,
|
||||
Count: 0,
|
||||
Limit: 0,
|
||||
Offset: 0,
|
||||
}
|
||||
h.sendFormattedResponse(w, tableMetadata, responseMetadata, options)
|
||||
}
|
||||
|
||||
// handleMeta processes meta operation requests
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !integration
|
||||
|
||||
package restheadspec
|
||||
|
||||
import (
|
||||
|
||||
46
pkg/restheadspec/headers_test.go
Normal file
46
pkg/restheadspec/headers_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package restheadspec
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecodeHeaderValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "Normal string",
|
||||
input: "test",
|
||||
expected: "test",
|
||||
},
|
||||
{
|
||||
name: "String without encoding prefix",
|
||||
input: "hello world",
|
||||
expected: "hello world",
|
||||
},
|
||||
{
|
||||
name: "Empty string",
|
||||
input: "",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := decodeHeaderValue(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("Expected '%s', got '%s'", tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Note: The following functions are unexported (lowercase) and cannot be tested directly:
|
||||
// - parseSelectFields
|
||||
// - parseFieldFilter
|
||||
// - mapSearchOperator
|
||||
// - parseCommaSeparated
|
||||
// - parseSorting
|
||||
// These are tested indirectly through parseOptionsFromHeaders in query_params_test.go
|
||||
556
pkg/restheadspec/integration_test.go
Normal file
556
pkg/restheadspec/integration_test.go
Normal file
@@ -0,0 +1,556 @@
|
||||
// +build integration
|
||||
|
||||
package restheadspec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
|
||||
"github.com/bitechdev/ResolveSpec/pkg/common"
|
||||
)
|
||||
|
||||
// Test models
|
||||
type TestUser struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"not null" json:"name"`
|
||||
Email string `gorm:"uniqueIndex;not null" json:"email"`
|
||||
Age int `json:"age"`
|
||||
Active bool `gorm:"default:true" json:"active"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Posts []TestPost `gorm:"foreignKey:UserID" json:"posts,omitempty"`
|
||||
}
|
||||
|
||||
func (TestUser) TableName() string {
|
||||
return "test_users"
|
||||
}
|
||||
|
||||
type TestPost struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
UserID uint `gorm:"not null" json:"user_id"`
|
||||
Title string `gorm:"not null" json:"title"`
|
||||
Content string `json:"content"`
|
||||
Published bool `gorm:"default:false" json:"published"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
User *TestUser `gorm:"foreignKey:UserID" json:"user,omitempty"`
|
||||
Comments []TestComment `gorm:"foreignKey:PostID" json:"comments,omitempty"`
|
||||
}
|
||||
|
||||
func (TestPost) TableName() string {
|
||||
return "test_posts"
|
||||
}
|
||||
|
||||
type TestComment struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
PostID uint `gorm:"not null" json:"post_id"`
|
||||
Content string `gorm:"not null" json:"content"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Post *TestPost `gorm:"foreignKey:PostID" json:"post,omitempty"`
|
||||
}
|
||||
|
||||
func (TestComment) TableName() string {
|
||||
return "test_comments"
|
||||
}
|
||||
|
||||
// Test helper functions
|
||||
func setupTestDB(t *testing.T) *gorm.DB {
|
||||
// Get connection string from environment or use default
|
||||
dsn := os.Getenv("TEST_DATABASE_URL")
|
||||
if dsn == "" {
|
||||
dsn = "host=localhost user=postgres password=postgres dbname=restheadspec_test port=5434 sslmode=disable"
|
||||
}
|
||||
|
||||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
})
|
||||
if err != nil {
|
||||
t.Skipf("Skipping integration test: database not available: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run migrations
|
||||
err = db.AutoMigrate(&TestUser{}, &TestPost{}, &TestComment{})
|
||||
if err != nil {
|
||||
t.Skipf("Skipping integration test: failed to migrate database: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func cleanupTestDB(t *testing.T, db *gorm.DB) {
|
||||
// Clean up test data
|
||||
db.Exec("TRUNCATE TABLE test_comments CASCADE")
|
||||
db.Exec("TRUNCATE TABLE test_posts CASCADE")
|
||||
db.Exec("TRUNCATE TABLE test_users CASCADE")
|
||||
}
|
||||
|
||||
func createTestData(t *testing.T, db *gorm.DB) {
|
||||
users := []TestUser{
|
||||
{Name: "John Doe", Email: "john@example.com", Age: 30, Active: true},
|
||||
{Name: "Jane Smith", Email: "jane@example.com", Age: 25, Active: true},
|
||||
{Name: "Bob Johnson", Email: "bob@example.com", Age: 35, Active: false},
|
||||
}
|
||||
|
||||
for i := range users {
|
||||
if err := db.Create(&users[i]).Error; err != nil {
|
||||
t.Fatalf("Failed to create test user: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
posts := []TestPost{
|
||||
{UserID: users[0].ID, Title: "First Post", Content: "Hello World", Published: true},
|
||||
{UserID: users[0].ID, Title: "Second Post", Content: "More content", Published: true},
|
||||
{UserID: users[1].ID, Title: "Jane's Post", Content: "Jane's content", Published: false},
|
||||
}
|
||||
|
||||
for i := range posts {
|
||||
if err := db.Create(&posts[i]).Error; err != nil {
|
||||
t.Fatalf("Failed to create test post: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
comments := []TestComment{
|
||||
{PostID: posts[0].ID, Content: "Great post!"},
|
||||
{PostID: posts[0].ID, Content: "Thanks for sharing"},
|
||||
{PostID: posts[1].ID, Content: "Interesting"},
|
||||
}
|
||||
|
||||
for i := range comments {
|
||||
if err := db.Create(&comments[i]).Error; err != nil {
|
||||
t.Fatalf("Failed to create test comment: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Integration tests
|
||||
func TestIntegration_GetAllUsers(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer cleanupTestDB(t, db)
|
||||
createTestData(t, db)
|
||||
|
||||
handler := NewHandlerWithGORM(db)
|
||||
handler.registry.RegisterModel("public.test_users", TestUser{})
|
||||
|
||||
muxRouter := mux.NewRouter()
|
||||
SetupMuxRoutes(muxRouter, handler, nil)
|
||||
|
||||
req := httptest.NewRequest("GET", "/public/test_users", nil)
|
||||
req.Header.Set("X-DetailApi", "true")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
muxRouter.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status 200, got %d. Body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var response common.Response
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
||||
t.Fatalf("Failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
t.Errorf("Expected success=true, got %v", response.Success)
|
||||
}
|
||||
|
||||
if response.Metadata == nil {
|
||||
t.Fatal("Expected metadata, got nil")
|
||||
}
|
||||
|
||||
if response.Metadata.Total != 3 {
|
||||
t.Errorf("Expected 3 users, got %d", response.Metadata.Total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegration_GetUsersWithFilters(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer cleanupTestDB(t, db)
|
||||
createTestData(t, db)
|
||||
|
||||
handler := NewHandlerWithGORM(db)
|
||||
handler.registry.RegisterModel("public.test_users", TestUser{})
|
||||
|
||||
muxRouter := mux.NewRouter()
|
||||
SetupMuxRoutes(muxRouter, handler, nil)
|
||||
|
||||
// Filter: age > 25
|
||||
req := httptest.NewRequest("GET", "/public/test_users", nil)
|
||||
req.Header.Set("X-DetailApi", "true")
|
||||
req.Header.Set("X-SearchOp-Gt-Age", "25")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
muxRouter.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status 200, got %d. Body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var response common.Response
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
||||
t.Fatalf("Failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
t.Errorf("Expected success=true, got %v", response.Success)
|
||||
}
|
||||
|
||||
// Should return 2 users (John: 30, Bob: 35)
|
||||
if response.Metadata.Total != 2 {
|
||||
t.Errorf("Expected 2 filtered users, got %d", response.Metadata.Total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegration_GetUsersWithPagination(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer cleanupTestDB(t, db)
|
||||
createTestData(t, db)
|
||||
|
||||
handler := NewHandlerWithGORM(db)
|
||||
handler.registry.RegisterModel("public.test_users", TestUser{})
|
||||
|
||||
muxRouter := mux.NewRouter()
|
||||
SetupMuxRoutes(muxRouter, handler, nil)
|
||||
|
||||
req := httptest.NewRequest("GET", "/public/test_users", nil)
|
||||
req.Header.Set("X-DetailApi", "true")
|
||||
req.Header.Set("X-Limit", "2")
|
||||
req.Header.Set("X-Offset", "1")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
muxRouter.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
var response common.Response
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
||||
t.Fatalf("Failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
t.Errorf("Expected success=true, got %v", response.Success)
|
||||
}
|
||||
|
||||
// Total should still be 3, but we're only retrieving 2 records starting from offset 1
|
||||
if response.Metadata.Total != 3 {
|
||||
t.Errorf("Expected total 3 users, got %d", response.Metadata.Total)
|
||||
}
|
||||
|
||||
if response.Metadata.Limit != 2 {
|
||||
t.Errorf("Expected limit 2, got %d", response.Metadata.Limit)
|
||||
}
|
||||
|
||||
if response.Metadata.Offset != 1 {
|
||||
t.Errorf("Expected offset 1, got %d", response.Metadata.Offset)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegration_GetUsersWithSorting(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer cleanupTestDB(t, db)
|
||||
createTestData(t, db)
|
||||
|
||||
handler := NewHandlerWithGORM(db)
|
||||
handler.registry.RegisterModel("public.test_users", TestUser{})
|
||||
|
||||
muxRouter := mux.NewRouter()
|
||||
SetupMuxRoutes(muxRouter, handler, nil)
|
||||
|
||||
// Sort by age descending
|
||||
req := httptest.NewRequest("GET", "/public/test_users", nil)
|
||||
req.Header.Set("X-DetailApi", "true")
|
||||
req.Header.Set("X-Sort", "-age")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
muxRouter.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
var response common.Response
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
||||
t.Fatalf("Failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
t.Errorf("Expected success=true, got %v", response.Success)
|
||||
}
|
||||
|
||||
// Parse data to verify sort order
|
||||
dataBytes, _ := json.Marshal(response.Data)
|
||||
var users []TestUser
|
||||
json.Unmarshal(dataBytes, &users)
|
||||
|
||||
if len(users) < 3 {
|
||||
t.Fatal("Expected at least 3 users")
|
||||
}
|
||||
|
||||
// Check that users are sorted by age descending (Bob:35, John:30, Jane:25)
|
||||
if users[0].Age < users[1].Age || users[1].Age < users[2].Age {
|
||||
t.Error("Expected users to be sorted by age descending")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegration_GetUsersWithColumnsSelection(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer cleanupTestDB(t, db)
|
||||
createTestData(t, db)
|
||||
|
||||
handler := NewHandlerWithGORM(db)
|
||||
handler.registry.RegisterModel("public.test_users", TestUser{})
|
||||
|
||||
muxRouter := mux.NewRouter()
|
||||
SetupMuxRoutes(muxRouter, handler, nil)
|
||||
|
||||
req := httptest.NewRequest("GET", "/public/test_users", nil)
|
||||
req.Header.Set("X-DetailApi", "true")
|
||||
req.Header.Set("X-Columns", "id,name,email")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
muxRouter.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status 200, got %d. Body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var response common.Response
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
||||
t.Fatalf("Failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
t.Errorf("Expected success=true, got %v", response.Success)
|
||||
}
|
||||
|
||||
// Verify data was returned (column selection doesn't affect metadata count)
|
||||
if response.Metadata.Total != 3 {
|
||||
t.Errorf("Expected 3 users, got %d", response.Metadata.Total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegration_GetUsersWithPreload(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer cleanupTestDB(t, db)
|
||||
createTestData(t, db)
|
||||
|
||||
handler := NewHandlerWithGORM(db)
|
||||
handler.registry.RegisterModel("public.test_users", TestUser{})
|
||||
|
||||
muxRouter := mux.NewRouter()
|
||||
SetupMuxRoutes(muxRouter, handler, nil)
|
||||
|
||||
req := httptest.NewRequest("GET", "/public/test_users?x-fieldfilter-email=john@example.com", nil)
|
||||
req.Header.Set("X-DetailApi", "true")
|
||||
req.Header.Set("X-Preload", "Posts")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
muxRouter.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status 200, got %d. Body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var response common.Response
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
||||
t.Fatalf("Failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
t.Errorf("Expected success=true, got %v", response.Success)
|
||||
}
|
||||
|
||||
// Verify posts are preloaded
|
||||
dataBytes, _ := json.Marshal(response.Data)
|
||||
var users []TestUser
|
||||
json.Unmarshal(dataBytes, &users)
|
||||
|
||||
if len(users) == 0 {
|
||||
t.Fatal("Expected at least one user")
|
||||
}
|
||||
|
||||
if len(users[0].Posts) == 0 {
|
||||
t.Error("Expected posts to be preloaded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegration_GetMetadata(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer cleanupTestDB(t, db)
|
||||
|
||||
handler := NewHandlerWithGORM(db)
|
||||
handler.registry.RegisterModel("public.test_users", TestUser{})
|
||||
|
||||
muxRouter := mux.NewRouter()
|
||||
SetupMuxRoutes(muxRouter, handler, nil)
|
||||
|
||||
req := httptest.NewRequest("GET", "/public/test_users/metadata", nil)
|
||||
req.Header.Set("X-DetailApi", "true")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
muxRouter.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status 200, got %d. Body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var response common.Response
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
||||
t.Fatalf("Failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
t.Errorf("Expected success=true, got %v. Body: %s, Error: %v", response.Success, w.Body.String(), response.Error)
|
||||
}
|
||||
|
||||
// Check that metadata includes columns
|
||||
metadataBytes, _ := json.Marshal(response.Data)
|
||||
var metadata common.TableMetadata
|
||||
json.Unmarshal(metadataBytes, &metadata)
|
||||
|
||||
if len(metadata.Columns) == 0 {
|
||||
t.Error("Expected metadata to contain columns")
|
||||
}
|
||||
|
||||
// Verify some expected columns
|
||||
hasID := false
|
||||
hasName := false
|
||||
hasEmail := false
|
||||
for _, col := range metadata.Columns {
|
||||
if col.Name == "id" {
|
||||
hasID = true
|
||||
}
|
||||
if col.Name == "name" {
|
||||
hasName = true
|
||||
}
|
||||
if col.Name == "email" {
|
||||
hasEmail = true
|
||||
}
|
||||
}
|
||||
|
||||
if !hasID || !hasName || !hasEmail {
|
||||
t.Error("Expected metadata to contain 'id', 'name', and 'email' columns")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegration_OptionsRequest(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer cleanupTestDB(t, db)
|
||||
|
||||
handler := NewHandlerWithGORM(db)
|
||||
handler.registry.RegisterModel("public.test_users", TestUser{})
|
||||
|
||||
muxRouter := mux.NewRouter()
|
||||
SetupMuxRoutes(muxRouter, handler, nil)
|
||||
|
||||
req := httptest.NewRequest("OPTIONS", "/public/test_users", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
muxRouter.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
// Check CORS headers
|
||||
if w.Header().Get("Access-Control-Allow-Origin") == "" {
|
||||
t.Error("Expected Access-Control-Allow-Origin header")
|
||||
}
|
||||
|
||||
if w.Header().Get("Access-Control-Allow-Methods") == "" {
|
||||
t.Error("Expected Access-Control-Allow-Methods header")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegration_QueryParamsOverHeaders(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer cleanupTestDB(t, db)
|
||||
createTestData(t, db)
|
||||
|
||||
handler := NewHandlerWithGORM(db)
|
||||
handler.registry.RegisterModel("public.test_users", TestUser{})
|
||||
|
||||
muxRouter := mux.NewRouter()
|
||||
SetupMuxRoutes(muxRouter, handler, nil)
|
||||
|
||||
// Query params should override headers
|
||||
req := httptest.NewRequest("GET", "/public/test_users?x-limit=1", nil)
|
||||
req.Header.Set("X-DetailApi", "true")
|
||||
req.Header.Set("X-Limit", "10")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
muxRouter.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
var response common.Response
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
||||
t.Fatalf("Failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
t.Errorf("Expected success=true, got %v", response.Success)
|
||||
}
|
||||
|
||||
// Query param should win (limit=1)
|
||||
if response.Metadata.Limit != 1 {
|
||||
t.Errorf("Expected limit 1 from query param, got %d", response.Metadata.Limit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegration_GetSingleRecord(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer cleanupTestDB(t, db)
|
||||
createTestData(t, db)
|
||||
|
||||
handler := NewHandlerWithGORM(db)
|
||||
handler.registry.RegisterModel("public.test_users", TestUser{})
|
||||
|
||||
muxRouter := mux.NewRouter()
|
||||
SetupMuxRoutes(muxRouter, handler, nil)
|
||||
|
||||
// Get first user ID
|
||||
var user TestUser
|
||||
db.Where("email = ?", "john@example.com").First(&user)
|
||||
|
||||
req := httptest.NewRequest("GET", fmt.Sprintf("/public/test_users/%d", user.ID), nil)
|
||||
req.Header.Set("X-DetailApi", "true")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
muxRouter.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status 200, got %d. Body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var response common.Response
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
||||
t.Fatalf("Failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
t.Errorf("Expected success=true, got %v", response.Success)
|
||||
}
|
||||
|
||||
// Verify it's a single record
|
||||
dataBytes, _ := json.Marshal(response.Data)
|
||||
var resultUser TestUser
|
||||
json.Unmarshal(dataBytes, &resultUser)
|
||||
|
||||
if resultUser.Email != "john@example.com" {
|
||||
t.Errorf("Expected user with email 'john@example.com', got '%s'", resultUser.Email)
|
||||
}
|
||||
}
|
||||
@@ -128,15 +128,17 @@ func SetupMuxRoutes(muxRouter *mux.Router, handler *Handler, authMiddleware Midd
|
||||
}
|
||||
|
||||
// Register routes for this entity
|
||||
// IMPORTANT: Register more specific routes before wildcard routes
|
||||
|
||||
// GET, POST for /{schema}/{entity}
|
||||
muxRouter.Handle(entityPath, entityHandler).Methods("GET", "POST")
|
||||
|
||||
// GET for metadata (using HandleGet) - MUST be registered before /{id} route
|
||||
muxRouter.Handle(metadataPath, metadataHandler).Methods("GET")
|
||||
|
||||
// GET, PUT, PATCH, DELETE, POST for /{schema}/{entity}/{id}
|
||||
muxRouter.Handle(entityWithIDPath, entityWithIDHandler).Methods("GET", "PUT", "PATCH", "DELETE", "POST")
|
||||
|
||||
// GET for metadata (using HandleGet)
|
||||
muxRouter.Handle(metadataPath, metadataHandler).Methods("GET")
|
||||
|
||||
// OPTIONS for CORS preflight - returns metadata
|
||||
muxRouter.Handle(entityPath, optionsEntityHandler).Methods("OPTIONS")
|
||||
muxRouter.Handle(entityWithIDPath, optionsEntityWithIDHandler).Methods("OPTIONS")
|
||||
|
||||
114
pkg/restheadspec/restheadspec_test.go
Normal file
114
pkg/restheadspec/restheadspec_test.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package restheadspec
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseModelName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fullName string
|
||||
expectedSchema string
|
||||
expectedEntity string
|
||||
}{
|
||||
{
|
||||
name: "Model with schema",
|
||||
fullName: "public.users",
|
||||
expectedSchema: "public",
|
||||
expectedEntity: "users",
|
||||
},
|
||||
{
|
||||
name: "Model without schema",
|
||||
fullName: "users",
|
||||
expectedSchema: "",
|
||||
expectedEntity: "users",
|
||||
},
|
||||
{
|
||||
name: "Model with custom schema",
|
||||
fullName: "myschema.products",
|
||||
expectedSchema: "myschema",
|
||||
expectedEntity: "products",
|
||||
},
|
||||
{
|
||||
name: "Empty string",
|
||||
fullName: "",
|
||||
expectedSchema: "",
|
||||
expectedEntity: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
schema, entity := parseModelName(tt.fullName)
|
||||
if schema != tt.expectedSchema {
|
||||
t.Errorf("Expected schema '%s', got '%s'", tt.expectedSchema, schema)
|
||||
}
|
||||
if entity != tt.expectedEntity {
|
||||
t.Errorf("Expected entity '%s', got '%s'", tt.expectedEntity, entity)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildRoutePath(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
schema string
|
||||
entity string
|
||||
expectedPath string
|
||||
}{
|
||||
{
|
||||
name: "With schema",
|
||||
schema: "public",
|
||||
entity: "users",
|
||||
expectedPath: "/public/users",
|
||||
},
|
||||
{
|
||||
name: "Without schema",
|
||||
schema: "",
|
||||
entity: "users",
|
||||
expectedPath: "/users",
|
||||
},
|
||||
{
|
||||
name: "Custom schema",
|
||||
schema: "admin",
|
||||
entity: "logs",
|
||||
expectedPath: "/admin/logs",
|
||||
},
|
||||
{
|
||||
name: "Empty entity with schema",
|
||||
schema: "public",
|
||||
entity: "",
|
||||
expectedPath: "/public/",
|
||||
},
|
||||
{
|
||||
name: "Both empty",
|
||||
schema: "",
|
||||
entity: "",
|
||||
expectedPath: "/",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
path := buildRoutePath(tt.schema, tt.entity)
|
||||
if path != tt.expectedPath {
|
||||
t.Errorf("Expected path '%s', got '%s'", tt.expectedPath, path)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewStandardMuxRouter(t *testing.T) {
|
||||
router := NewStandardMuxRouter()
|
||||
if router == nil {
|
||||
t.Error("Expected router to be created, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewStandardBunRouter(t *testing.T) {
|
||||
router := NewStandardBunRouter()
|
||||
if router == nil {
|
||||
t.Error("Expected router to be created, got nil")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user