mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-13 17:10:36 +00:00
557 lines
15 KiB
Go
557 lines
15 KiB
Go
// +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)
|
|
}
|
|
}
|