mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-13 17:10:36 +00:00
Some checks are pending
Build , Vet Test, and Lint / Run Vet Tests (1.23.x) (push) Waiting to run
Build , Vet Test, and Lint / Run Vet Tests (1.24.x) (push) Waiting to run
Build , Vet Test, and Lint / Lint Code (push) Waiting to run
Build , Vet Test, and Lint / Build (push) Waiting to run
Tests / Integration Tests (push) Waiting to run
Tests / Unit Tests (push) Waiting to run
527 lines
13 KiB
Go
527 lines
13 KiB
Go
// +build integration
|
|
|
|
package database
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
_ "github.com/jackc/pgx/v5/stdlib"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/testcontainers/testcontainers-go"
|
|
"github.com/testcontainers/testcontainers-go/wait"
|
|
)
|
|
|
|
// Integration test models
|
|
type IntegrationUser struct {
|
|
ID int `db:"id"`
|
|
Name string `db:"name"`
|
|
Email string `db:"email"`
|
|
Age int `db:"age"`
|
|
CreatedAt time.Time `db:"created_at"`
|
|
Posts []*IntegrationPost `bun:"rel:has-many,join:id=user_id"`
|
|
}
|
|
|
|
func (u IntegrationUser) TableName() string {
|
|
return "users"
|
|
}
|
|
|
|
type IntegrationPost struct {
|
|
ID int `db:"id"`
|
|
Title string `db:"title"`
|
|
Content string `db:"content"`
|
|
UserID int `db:"user_id"`
|
|
Published bool `db:"published"`
|
|
CreatedAt time.Time `db:"created_at"`
|
|
User *IntegrationUser `bun:"rel:belongs-to,join:user_id=id"`
|
|
Comments []*IntegrationComment `bun:"rel:has-many,join:id=post_id"`
|
|
}
|
|
|
|
func (p IntegrationPost) TableName() string {
|
|
return "posts"
|
|
}
|
|
|
|
type IntegrationComment struct {
|
|
ID int `db:"id"`
|
|
Content string `db:"content"`
|
|
PostID int `db:"post_id"`
|
|
CreatedAt time.Time `db:"created_at"`
|
|
Post *IntegrationPost `bun:"rel:belongs-to,join:post_id=id"`
|
|
}
|
|
|
|
func (c IntegrationComment) TableName() string {
|
|
return "comments"
|
|
}
|
|
|
|
// setupTestDB creates a PostgreSQL container and returns the connection
|
|
func setupTestDB(t *testing.T) (*sql.DB, func()) {
|
|
ctx := context.Background()
|
|
|
|
req := testcontainers.ContainerRequest{
|
|
Image: "postgres:15-alpine",
|
|
ExposedPorts: []string{"5432/tcp"},
|
|
Env: map[string]string{
|
|
"POSTGRES_USER": "testuser",
|
|
"POSTGRES_PASSWORD": "testpass",
|
|
"POSTGRES_DB": "testdb",
|
|
},
|
|
WaitingFor: wait.ForLog("database system is ready to accept connections").
|
|
WithOccurrence(2).
|
|
WithStartupTimeout(60 * time.Second),
|
|
}
|
|
|
|
postgres, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
|
ContainerRequest: req,
|
|
Started: true,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
host, err := postgres.Host(ctx)
|
|
require.NoError(t, err)
|
|
|
|
port, err := postgres.MappedPort(ctx, "5432")
|
|
require.NoError(t, err)
|
|
|
|
dsn := fmt.Sprintf("postgres://testuser:testpass@%s:%s/testdb?sslmode=disable",
|
|
host, port.Port())
|
|
|
|
db, err := sql.Open("pgx", dsn)
|
|
require.NoError(t, err)
|
|
|
|
// Wait for database to be ready
|
|
err = db.Ping()
|
|
require.NoError(t, err)
|
|
|
|
// Create schema
|
|
createSchema(t, db)
|
|
|
|
cleanup := func() {
|
|
db.Close()
|
|
postgres.Terminate(ctx)
|
|
}
|
|
|
|
return db, cleanup
|
|
}
|
|
|
|
// createSchema creates test tables
|
|
func createSchema(t *testing.T, db *sql.DB) {
|
|
schema := `
|
|
DROP TABLE IF EXISTS comments CASCADE;
|
|
DROP TABLE IF EXISTS posts CASCADE;
|
|
DROP TABLE IF EXISTS users CASCADE;
|
|
|
|
CREATE TABLE users (
|
|
id SERIAL PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
email VARCHAR(255) UNIQUE NOT NULL,
|
|
age INT NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE posts (
|
|
id SERIAL PRIMARY KEY,
|
|
title VARCHAR(255) NOT NULL,
|
|
content TEXT NOT NULL,
|
|
user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
published BOOLEAN DEFAULT false,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE comments (
|
|
id SERIAL PRIMARY KEY,
|
|
content TEXT NOT NULL,
|
|
post_id INT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
`
|
|
|
|
_, err := db.Exec(schema)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// TestIntegration_BasicCRUD tests basic CRUD operations
|
|
func TestIntegration_BasicCRUD(t *testing.T) {
|
|
db, cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
adapter := NewPgSQLAdapter(db)
|
|
ctx := context.Background()
|
|
|
|
// CREATE
|
|
result, err := adapter.NewInsert().
|
|
Table("users").
|
|
Value("name", "John Doe").
|
|
Value("email", "john@example.com").
|
|
Value("age", 25).
|
|
Exec(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), result.RowsAffected())
|
|
|
|
// READ
|
|
var users []IntegrationUser
|
|
err = adapter.NewSelect().
|
|
Table("users").
|
|
Where("email = ?", "john@example.com").
|
|
Scan(ctx, &users)
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, users, 1)
|
|
assert.Equal(t, "John Doe", users[0].Name)
|
|
assert.Equal(t, 25, users[0].Age)
|
|
|
|
userID := users[0].ID
|
|
|
|
// UPDATE
|
|
result, err = adapter.NewUpdate().
|
|
Table("users").
|
|
Set("age", 26).
|
|
Where("id = ?", userID).
|
|
Exec(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), result.RowsAffected())
|
|
|
|
// Verify update
|
|
var updatedUser IntegrationUser
|
|
err = adapter.NewSelect().
|
|
Table("users").
|
|
Where("id = ?", userID).
|
|
Scan(ctx, &updatedUser)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 26, updatedUser.Age)
|
|
|
|
// DELETE
|
|
result, err = adapter.NewDelete().
|
|
Table("users").
|
|
Where("id = ?", userID).
|
|
Exec(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), result.RowsAffected())
|
|
|
|
// Verify delete
|
|
count, err := adapter.NewSelect().
|
|
Table("users").
|
|
Where("id = ?", userID).
|
|
Count(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, count)
|
|
}
|
|
|
|
// TestIntegration_ScanModel tests ScanModel functionality
|
|
func TestIntegration_ScanModel(t *testing.T) {
|
|
db, cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
adapter := NewPgSQLAdapter(db)
|
|
ctx := context.Background()
|
|
|
|
// Insert test data
|
|
_, err := adapter.NewInsert().
|
|
Table("users").
|
|
Value("name", "Jane Smith").
|
|
Value("email", "jane@example.com").
|
|
Value("age", 30).
|
|
Exec(ctx)
|
|
require.NoError(t, err)
|
|
|
|
// Test single struct scan
|
|
user := &IntegrationUser{}
|
|
err = adapter.NewSelect().
|
|
Model(user).
|
|
Table("users").
|
|
Where("email = ?", "jane@example.com").
|
|
ScanModel(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "Jane Smith", user.Name)
|
|
assert.Equal(t, 30, user.Age)
|
|
|
|
// Test slice scan
|
|
users := []*IntegrationUser{}
|
|
err = adapter.NewSelect().
|
|
Model(&users).
|
|
Table("users").
|
|
ScanModel(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, users, 1)
|
|
}
|
|
|
|
// TestIntegration_Transaction tests transaction handling
|
|
func TestIntegration_Transaction(t *testing.T) {
|
|
db, cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
adapter := NewPgSQLAdapter(db)
|
|
ctx := context.Background()
|
|
|
|
// Successful transaction
|
|
err := adapter.RunInTransaction(ctx, func(tx common.Database) error {
|
|
_, err := tx.NewInsert().
|
|
Table("users").
|
|
Value("name", "Alice").
|
|
Value("email", "alice@example.com").
|
|
Value("age", 28).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = tx.NewInsert().
|
|
Table("users").
|
|
Value("name", "Bob").
|
|
Value("email", "bob@example.com").
|
|
Value("age", 32).
|
|
Exec(ctx)
|
|
return err
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
// Verify both records exist
|
|
count, err := adapter.NewSelect().
|
|
Table("users").
|
|
Count(ctx)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 2, count)
|
|
|
|
// Failed transaction (should rollback)
|
|
err = adapter.RunInTransaction(ctx, func(tx common.Database) error {
|
|
_, err := tx.NewInsert().
|
|
Table("users").
|
|
Value("name", "Charlie").
|
|
Value("email", "charlie@example.com").
|
|
Value("age", 35).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Intentional error - duplicate email
|
|
_, err = tx.NewInsert().
|
|
Table("users").
|
|
Value("name", "David").
|
|
Value("email", "alice@example.com"). // Duplicate
|
|
Value("age", 40).
|
|
Exec(ctx)
|
|
return err
|
|
})
|
|
|
|
assert.Error(t, err)
|
|
|
|
// Verify rollback - count should still be 2
|
|
count, err = adapter.NewSelect().
|
|
Table("users").
|
|
Count(ctx)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 2, count)
|
|
}
|
|
|
|
// TestIntegration_Preload tests basic preload functionality
|
|
func TestIntegration_Preload(t *testing.T) {
|
|
db, cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
adapter := NewPgSQLAdapter(db)
|
|
ctx := context.Background()
|
|
|
|
// Create test data
|
|
userID := createTestUser(t, adapter, ctx, "John Doe", "john@example.com", 25)
|
|
createTestPost(t, adapter, ctx, userID, "First Post", "Content 1", true)
|
|
createTestPost(t, adapter, ctx, userID, "Second Post", "Content 2", false)
|
|
|
|
// Test Preload
|
|
var users []*IntegrationUser
|
|
err := adapter.NewSelect().
|
|
Model(&IntegrationUser{}).
|
|
Table("users").
|
|
Preload("Posts").
|
|
Scan(ctx, &users)
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, users, 1)
|
|
assert.NotNil(t, users[0].Posts)
|
|
assert.Len(t, users[0].Posts, 2)
|
|
}
|
|
|
|
// TestIntegration_PreloadRelation tests smart PreloadRelation
|
|
func TestIntegration_PreloadRelation(t *testing.T) {
|
|
db, cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
adapter := NewPgSQLAdapter(db)
|
|
ctx := context.Background()
|
|
|
|
// Create test data
|
|
userID := createTestUser(t, adapter, ctx, "Jane Smith", "jane@example.com", 30)
|
|
postID := createTestPost(t, adapter, ctx, userID, "Test Post", "Test Content", true)
|
|
createTestComment(t, adapter, ctx, postID, "Great post!")
|
|
createTestComment(t, adapter, ctx, postID, "Thanks for sharing!")
|
|
|
|
// Test PreloadRelation with belongs-to (should use JOIN)
|
|
var posts []*IntegrationPost
|
|
err := adapter.NewSelect().
|
|
Model(&IntegrationPost{}).
|
|
Table("posts").
|
|
PreloadRelation("User").
|
|
Scan(ctx, &posts)
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, posts, 1)
|
|
// Note: JOIN preloading needs proper column selection to work
|
|
// For now, we test that it doesn't error
|
|
|
|
// Test PreloadRelation with has-many (should use subquery)
|
|
posts = []*IntegrationPost{}
|
|
err = adapter.NewSelect().
|
|
Model(&IntegrationPost{}).
|
|
Table("posts").
|
|
PreloadRelation("Comments").
|
|
Scan(ctx, &posts)
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, posts, 1)
|
|
if posts[0].Comments != nil {
|
|
assert.Len(t, posts[0].Comments, 2)
|
|
}
|
|
}
|
|
|
|
// TestIntegration_JoinRelation tests explicit JoinRelation
|
|
func TestIntegration_JoinRelation(t *testing.T) {
|
|
db, cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
adapter := NewPgSQLAdapter(db)
|
|
ctx := context.Background()
|
|
|
|
// Create test data
|
|
userID := createTestUser(t, adapter, ctx, "Bob Wilson", "bob@example.com", 35)
|
|
createTestPost(t, adapter, ctx, userID, "Join Test", "Content", true)
|
|
|
|
// Test JoinRelation
|
|
var posts []*IntegrationPost
|
|
err := adapter.NewSelect().
|
|
Model(&IntegrationPost{}).
|
|
Table("posts").
|
|
JoinRelation("User").
|
|
Scan(ctx, &posts)
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, posts, 1)
|
|
}
|
|
|
|
// TestIntegration_ComplexQuery tests complex queries
|
|
func TestIntegration_ComplexQuery(t *testing.T) {
|
|
db, cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
adapter := NewPgSQLAdapter(db)
|
|
ctx := context.Background()
|
|
|
|
// Create test data
|
|
userID1 := createTestUser(t, adapter, ctx, "Alice", "alice@example.com", 25)
|
|
userID2 := createTestUser(t, adapter, ctx, "Bob", "bob@example.com", 30)
|
|
userID3 := createTestUser(t, adapter, ctx, "Charlie", "charlie@example.com", 35)
|
|
|
|
createTestPost(t, adapter, ctx, userID1, "Post 1", "Content", true)
|
|
createTestPost(t, adapter, ctx, userID2, "Post 2", "Content", true)
|
|
createTestPost(t, adapter, ctx, userID3, "Post 3", "Content", false)
|
|
|
|
// Complex query with joins, where, order, limit
|
|
var results []map[string]interface{}
|
|
err := adapter.NewSelect().
|
|
Table("posts p").
|
|
Column("p.title", "u.name as author_name", "u.age as author_age").
|
|
LeftJoin("users u ON u.id = p.user_id").
|
|
Where("p.published = ?", true).
|
|
WhereOr("u.age > ?", 25).
|
|
Order("u.age DESC").
|
|
Limit(2).
|
|
Scan(ctx, &results)
|
|
|
|
require.NoError(t, err)
|
|
assert.LessOrEqual(t, len(results), 2)
|
|
}
|
|
|
|
// TestIntegration_Aggregation tests aggregation queries
|
|
func TestIntegration_Aggregation(t *testing.T) {
|
|
db, cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
adapter := NewPgSQLAdapter(db)
|
|
ctx := context.Background()
|
|
|
|
// Create test data
|
|
createTestUser(t, adapter, ctx, "User 1", "user1@example.com", 20)
|
|
createTestUser(t, adapter, ctx, "User 2", "user2@example.com", 25)
|
|
createTestUser(t, adapter, ctx, "User 3", "user3@example.com", 30)
|
|
|
|
// Test Count
|
|
count, err := adapter.NewSelect().
|
|
Table("users").
|
|
Where("age >= ?", 25).
|
|
Count(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 2, count)
|
|
|
|
// Test Exists
|
|
exists, err := adapter.NewSelect().
|
|
Table("users").
|
|
Where("email = ?", "user1@example.com").
|
|
Exists(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.True(t, exists)
|
|
|
|
// Test Group By with aggregation
|
|
var results []map[string]interface{}
|
|
err = adapter.NewSelect().
|
|
Table("users").
|
|
Column("age", "COUNT(*) as count").
|
|
Group("age").
|
|
Having("COUNT(*) > ?", 0).
|
|
Order("age ASC").
|
|
Scan(ctx, &results)
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, results, 3)
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
func createTestUser(t *testing.T, adapter *PgSQLAdapter, ctx context.Context, name, email string, age int) int {
|
|
var userID int
|
|
err := adapter.Query(ctx, &userID,
|
|
"INSERT INTO users (name, email, age) VALUES ($1, $2, $3) RETURNING id",
|
|
name, email, age)
|
|
require.NoError(t, err)
|
|
return userID
|
|
}
|
|
|
|
func createTestPost(t *testing.T, adapter *PgSQLAdapter, ctx context.Context, userID int, title, content string, published bool) int {
|
|
var postID int
|
|
err := adapter.Query(ctx, &postID,
|
|
"INSERT INTO posts (title, content, user_id, published) VALUES ($1, $2, $3, $4) RETURNING id",
|
|
title, content, userID, published)
|
|
require.NoError(t, err)
|
|
return postID
|
|
}
|
|
|
|
func createTestComment(t *testing.T, adapter *PgSQLAdapter, ctx context.Context, postID int, content string) int {
|
|
var commentID int
|
|
err := adapter.Query(ctx, &commentID,
|
|
"INSERT INTO comments (content, post_id) VALUES ($1, $2) RETURNING id",
|
|
content, postID)
|
|
require.NoError(t, err)
|
|
return commentID
|
|
}
|