start working on the frontend side
This commit is contained in:
85
backend/tests/integration/README.md
Normal file
85
backend/tests/integration/README.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Integration Tests
|
||||
|
||||
This directory contains integration tests for the Calypso API.
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Create Test Database (Optional)
|
||||
|
||||
For isolated testing, create a separate test database:
|
||||
|
||||
```bash
|
||||
sudo -u postgres createdb calypso_test
|
||||
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE calypso_test TO calypso;"
|
||||
```
|
||||
|
||||
### 2. Environment Variables
|
||||
|
||||
Set test database configuration:
|
||||
|
||||
```bash
|
||||
export TEST_DB_HOST=localhost
|
||||
export TEST_DB_PORT=5432
|
||||
export TEST_DB_USER=calypso
|
||||
export TEST_DB_PASSWORD=calypso123
|
||||
export TEST_DB_NAME=calypso_test # or use existing 'calypso' database
|
||||
```
|
||||
|
||||
Or use the existing database:
|
||||
|
||||
```bash
|
||||
export TEST_DB_NAME=calypso
|
||||
export TEST_DB_PASSWORD=calypso123
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Run All Integration Tests
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
go test ./tests/integration/... -v
|
||||
```
|
||||
|
||||
### Run Specific Test
|
||||
|
||||
```bash
|
||||
go test ./tests/integration/... -run TestHealthEndpoint -v
|
||||
```
|
||||
|
||||
### Run with Coverage
|
||||
|
||||
```bash
|
||||
go test -cover ./tests/integration/... -v
|
||||
```
|
||||
|
||||
## Test Structure
|
||||
|
||||
- `setup.go` - Test database setup and helper functions
|
||||
- `api_test.go` - API endpoint integration tests
|
||||
|
||||
## Test Coverage
|
||||
|
||||
### ✅ Implemented Tests
|
||||
|
||||
1. **TestHealthEndpoint** - Tests enhanced health check endpoint
|
||||
2. **TestLoginEndpoint** - Tests user login with password verification
|
||||
3. **TestLoginEndpoint_WrongPassword** - Tests wrong password rejection
|
||||
4. **TestGetCurrentUser** - Tests authenticated user info retrieval
|
||||
5. **TestListAlerts** - Tests monitoring alerts endpoint
|
||||
|
||||
### ⏳ Future Tests
|
||||
|
||||
- Storage endpoints
|
||||
- SCST endpoints
|
||||
- VTL endpoints
|
||||
- Task management
|
||||
- IAM endpoints
|
||||
|
||||
## Notes
|
||||
|
||||
- Tests use the actual database (test or production)
|
||||
- Tests clean up data after each test run
|
||||
- Tests create test users with proper password hashing
|
||||
- Tests verify authentication and authorization
|
||||
|
||||
309
backend/tests/integration/api_test.go
Normal file
309
backend/tests/integration/api_test.go
Normal file
@@ -0,0 +1,309 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/atlasos/calypso/internal/common/config"
|
||||
"github.com/atlasos/calypso/internal/common/logger"
|
||||
"github.com/atlasos/calypso/internal/common/password"
|
||||
"github.com/atlasos/calypso/internal/common/router"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// Set Gin to test mode
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
// Run tests
|
||||
code := m.Run()
|
||||
|
||||
// Cleanup
|
||||
if TestDB != nil {
|
||||
TestDB.Close()
|
||||
}
|
||||
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func TestHealthEndpoint(t *testing.T) {
|
||||
db := SetupTestDB(t)
|
||||
defer CleanupTestDB(t)
|
||||
|
||||
cfg := TestConfig
|
||||
if cfg == nil {
|
||||
cfg = &config.Config{
|
||||
Server: config.ServerConfig{
|
||||
Port: 8080,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
log := TestLogger
|
||||
if log == nil {
|
||||
log = logger.NewLogger("test")
|
||||
}
|
||||
r := router.NewRouter(cfg, db, log)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/v1/health", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "calypso-api", response["service"])
|
||||
}
|
||||
|
||||
func TestLoginEndpoint(t *testing.T) {
|
||||
db := SetupTestDB(t)
|
||||
defer CleanupTestDB(t)
|
||||
|
||||
// Create test user
|
||||
passwordHash, err := password.HashPassword("testpass123", config.Argon2Params{
|
||||
Memory: 64 * 1024,
|
||||
Iterations: 3,
|
||||
Parallelism: 4,
|
||||
SaltLength: 16,
|
||||
KeyLength: 32,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
userID := CreateTestUser(t, "testuser", "test@example.com", passwordHash, false)
|
||||
|
||||
cfg := TestConfig
|
||||
if cfg == nil {
|
||||
cfg = &config.Config{
|
||||
Server: config.ServerConfig{
|
||||
Port: 8080,
|
||||
},
|
||||
Auth: config.AuthConfig{
|
||||
JWTSecret: "test-jwt-secret-key-minimum-32-characters-long",
|
||||
TokenLifetime: 24 * 60 * 60 * 1000000000, // 24 hours in nanoseconds
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
log := TestLogger
|
||||
if log == nil {
|
||||
log = logger.NewLogger("test")
|
||||
}
|
||||
r := router.NewRouter(cfg, db, log)
|
||||
|
||||
// Test login
|
||||
loginData := map[string]string{
|
||||
"username": "testuser",
|
||||
"password": "testpass123",
|
||||
}
|
||||
jsonData, _ := json.Marshal(loginData)
|
||||
|
||||
req := httptest.NewRequest("POST", "/api/v1/auth/login", bytes.NewBuffer(jsonData))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response map[string]interface{}
|
||||
err = json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, response["token"])
|
||||
assert.Equal(t, userID, response["user"].(map[string]interface{})["id"])
|
||||
}
|
||||
|
||||
func TestLoginEndpoint_WrongPassword(t *testing.T) {
|
||||
db := SetupTestDB(t)
|
||||
defer CleanupTestDB(t)
|
||||
|
||||
// Create test user
|
||||
passwordHash, err := password.HashPassword("testpass123", config.Argon2Params{
|
||||
Memory: 64 * 1024,
|
||||
Iterations: 3,
|
||||
Parallelism: 4,
|
||||
SaltLength: 16,
|
||||
KeyLength: 32,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
CreateTestUser(t, "testuser", "test@example.com", passwordHash, false)
|
||||
|
||||
cfg := TestConfig
|
||||
if cfg == nil {
|
||||
cfg = &config.Config{
|
||||
Server: config.ServerConfig{
|
||||
Port: 8080,
|
||||
},
|
||||
Auth: config.AuthConfig{
|
||||
JWTSecret: "test-jwt-secret-key-minimum-32-characters-long",
|
||||
TokenLifetime: 24 * 60 * 60 * 1000000000,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
log := TestLogger
|
||||
if log == nil {
|
||||
log = logger.NewLogger("test")
|
||||
}
|
||||
r := router.NewRouter(cfg, db, log)
|
||||
|
||||
// Test login with wrong password
|
||||
loginData := map[string]string{
|
||||
"username": "testuser",
|
||||
"password": "wrongpassword",
|
||||
}
|
||||
jsonData, _ := json.Marshal(loginData)
|
||||
|
||||
req := httptest.NewRequest("POST", "/api/v1/auth/login", bytes.NewBuffer(jsonData))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
}
|
||||
|
||||
func TestGetCurrentUser(t *testing.T) {
|
||||
db := SetupTestDB(t)
|
||||
defer CleanupTestDB(t)
|
||||
|
||||
// Create test user and get token
|
||||
passwordHash, err := password.HashPassword("testpass123", config.Argon2Params{
|
||||
Memory: 64 * 1024,
|
||||
Iterations: 3,
|
||||
Parallelism: 4,
|
||||
SaltLength: 16,
|
||||
KeyLength: 32,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
userID := CreateTestUser(t, "testuser", "test@example.com", passwordHash, false)
|
||||
|
||||
cfg := TestConfig
|
||||
if cfg == nil {
|
||||
cfg = &config.Config{
|
||||
Server: config.ServerConfig{
|
||||
Port: 8080,
|
||||
},
|
||||
Auth: config.AuthConfig{
|
||||
JWTSecret: "test-jwt-secret-key-minimum-32-characters-long",
|
||||
TokenLifetime: 24 * 60 * 60 * 1000000000,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
log := TestLogger
|
||||
if log == nil {
|
||||
log = logger.NewLogger("test")
|
||||
}
|
||||
|
||||
// Login to get token
|
||||
loginData := map[string]string{
|
||||
"username": "testuser",
|
||||
"password": "testpass123",
|
||||
}
|
||||
jsonData, _ := json.Marshal(loginData)
|
||||
|
||||
r := router.NewRouter(cfg, db, log)
|
||||
req := httptest.NewRequest("POST", "/api/v1/auth/login", bytes.NewBuffer(jsonData))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var loginResponse map[string]interface{}
|
||||
err = json.Unmarshal(w.Body.Bytes(), &loginResponse)
|
||||
require.NoError(t, err)
|
||||
token := loginResponse["token"].(string)
|
||||
|
||||
// Test /auth/me endpoint (use same router instance)
|
||||
req2 := httptest.NewRequest("GET", "/api/v1/auth/me", nil)
|
||||
req2.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
w2 := httptest.NewRecorder()
|
||||
r.ServeHTTP(w2, req2)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w2.Code)
|
||||
|
||||
var userResponse map[string]interface{}
|
||||
err = json.Unmarshal(w2.Body.Bytes(), &userResponse)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, userID, userResponse["id"])
|
||||
assert.Equal(t, "testuser", userResponse["username"])
|
||||
}
|
||||
|
||||
func TestListAlerts(t *testing.T) {
|
||||
db := SetupTestDB(t)
|
||||
defer CleanupTestDB(t)
|
||||
|
||||
// Create test user and get token
|
||||
passwordHash, err := password.HashPassword("testpass123", config.Argon2Params{
|
||||
Memory: 64 * 1024,
|
||||
Iterations: 3,
|
||||
Parallelism: 4,
|
||||
SaltLength: 16,
|
||||
KeyLength: 32,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
CreateTestUser(t, "testuser", "test@example.com", passwordHash, true) // Admin user
|
||||
|
||||
cfg := TestConfig
|
||||
if cfg == nil {
|
||||
cfg = &config.Config{
|
||||
Server: config.ServerConfig{
|
||||
Port: 8080,
|
||||
},
|
||||
Auth: config.AuthConfig{
|
||||
JWTSecret: "test-jwt-secret-key-minimum-32-characters-long",
|
||||
TokenLifetime: 24 * 60 * 60 * 1000000000,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
log := TestLogger
|
||||
if log == nil {
|
||||
log = logger.NewLogger("test")
|
||||
}
|
||||
r := router.NewRouter(cfg, db, log)
|
||||
|
||||
// Login to get token
|
||||
loginData := map[string]string{
|
||||
"username": "testuser",
|
||||
"password": "testpass123",
|
||||
}
|
||||
jsonData, _ := json.Marshal(loginData)
|
||||
|
||||
req := httptest.NewRequest("POST", "/api/v1/auth/login", bytes.NewBuffer(jsonData))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var loginResponse map[string]interface{}
|
||||
err = json.Unmarshal(w.Body.Bytes(), &loginResponse)
|
||||
require.NoError(t, err)
|
||||
token := loginResponse["token"].(string)
|
||||
|
||||
// Test /monitoring/alerts endpoint (use same router instance)
|
||||
req2 := httptest.NewRequest("GET", "/api/v1/monitoring/alerts", nil)
|
||||
req2.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
w2 := httptest.NewRecorder()
|
||||
r.ServeHTTP(w2, req2)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w2.Code)
|
||||
|
||||
var alertsResponse map[string]interface{}
|
||||
err = json.Unmarshal(w2.Body.Bytes(), &alertsResponse)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, alertsResponse["alerts"])
|
||||
}
|
||||
|
||||
174
backend/tests/integration/setup.go
Normal file
174
backend/tests/integration/setup.go
Normal file
@@ -0,0 +1,174 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/atlasos/calypso/internal/common/config"
|
||||
"github.com/atlasos/calypso/internal/common/database"
|
||||
"github.com/atlasos/calypso/internal/common/logger"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// TestDB holds the test database connection
|
||||
var TestDB *database.DB
|
||||
var TestConfig *config.Config
|
||||
var TestLogger *logger.Logger
|
||||
|
||||
// SetupTestDB initializes a test database connection
|
||||
func SetupTestDB(t *testing.T) *database.DB {
|
||||
if TestDB != nil {
|
||||
return TestDB
|
||||
}
|
||||
|
||||
// Get test database configuration from environment
|
||||
dbHost := getEnv("TEST_DB_HOST", "localhost")
|
||||
dbPort := getEnvInt("TEST_DB_PORT", 5432)
|
||||
dbUser := getEnv("TEST_DB_USER", "calypso")
|
||||
dbPassword := getEnv("TEST_DB_PASSWORD", "calypso123")
|
||||
dbName := getEnv("TEST_DB_NAME", "calypso_test")
|
||||
|
||||
cfg := &config.Config{
|
||||
Database: config.DatabaseConfig{
|
||||
Host: dbHost,
|
||||
Port: dbPort,
|
||||
User: dbUser,
|
||||
Password: dbPassword,
|
||||
Database: dbName,
|
||||
SSLMode: "disable",
|
||||
MaxConnections: 10,
|
||||
MaxIdleConns: 5,
|
||||
ConnMaxLifetime: 5 * time.Minute,
|
||||
},
|
||||
}
|
||||
|
||||
db, err := database.NewConnection(cfg.Database)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to connect to test database: %v", err)
|
||||
}
|
||||
|
||||
// Run migrations
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := database.RunMigrations(ctx, db); err != nil {
|
||||
t.Fatalf("Failed to run migrations: %v", err)
|
||||
}
|
||||
|
||||
TestDB = db
|
||||
TestConfig = cfg
|
||||
if TestLogger == nil {
|
||||
TestLogger = logger.NewLogger("test")
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
// CleanupTestDB cleans up test data
|
||||
func CleanupTestDB(t *testing.T) {
|
||||
if TestDB == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Clean up test data (but keep schema)
|
||||
tables := []string{
|
||||
"sessions",
|
||||
"audit_log",
|
||||
"tasks",
|
||||
"alerts",
|
||||
"user_roles",
|
||||
"role_permissions",
|
||||
"users",
|
||||
"scst_initiators",
|
||||
"scst_luns",
|
||||
"scst_targets",
|
||||
"disk_repositories",
|
||||
"physical_disks",
|
||||
"virtual_tapes",
|
||||
"virtual_tape_drives",
|
||||
"virtual_tape_libraries",
|
||||
"physical_tape_slots",
|
||||
"physical_tape_drives",
|
||||
"physical_tape_libraries",
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
query := fmt.Sprintf("TRUNCATE TABLE %s CASCADE", table)
|
||||
if _, err := TestDB.ExecContext(ctx, query); err != nil {
|
||||
// Ignore errors for tables that don't exist
|
||||
t.Logf("Warning: Failed to truncate %s: %v", table, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CreateTestUser creates a test user in the database
|
||||
func CreateTestUser(t *testing.T, username, email, passwordHash string, isAdmin bool) string {
|
||||
userID := uuid.New().String()
|
||||
|
||||
query := `
|
||||
INSERT INTO users (id, username, email, password_hash, full_name, is_active)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING id
|
||||
`
|
||||
|
||||
var id string
|
||||
err := TestDB.QueryRow(query, userID, username, email, passwordHash, "Test User", true).Scan(&id)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test user: %v", err)
|
||||
}
|
||||
|
||||
// Assign admin role if requested
|
||||
if isAdmin {
|
||||
roleQuery := `
|
||||
INSERT INTO user_roles (user_id, role_id)
|
||||
SELECT $1, id FROM roles WHERE name = 'admin'
|
||||
`
|
||||
if _, err := TestDB.Exec(roleQuery, id); err != nil {
|
||||
t.Fatalf("Failed to assign admin role: %v", err)
|
||||
}
|
||||
} else {
|
||||
// Assign operator role for non-admin users (gives monitoring:read permission)
|
||||
roleQuery := `
|
||||
INSERT INTO user_roles (user_id, role_id)
|
||||
SELECT $1, id FROM roles WHERE name = 'operator'
|
||||
`
|
||||
if _, err := TestDB.Exec(roleQuery, id); err != nil {
|
||||
// Operator role might not exist, try readonly
|
||||
roleQuery = `
|
||||
INSERT INTO user_roles (user_id, role_id)
|
||||
SELECT $1, id FROM roles WHERE name = 'readonly'
|
||||
`
|
||||
if _, err := TestDB.Exec(roleQuery, id); err != nil {
|
||||
t.Logf("Warning: Failed to assign role: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
func getEnv(key, defaultValue string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func getEnvInt(key string, defaultValue int) int {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
var result int
|
||||
if _, err := fmt.Sscanf(v, "%d", &result); err == nil {
|
||||
return result
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user