package sqlite import ( "database/sql" "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "git.warky.dev/wdevs/relspecgo/pkg/models" "git.warky.dev/wdevs/relspecgo/pkg/readers" ) // setupTestDatabase creates a temporary SQLite database with test data func setupTestDatabase(t *testing.T) string { tmpDir := t.TempDir() dbPath := filepath.Join(tmpDir, "test.db") db, err := sql.Open("sqlite", dbPath) require.NoError(t, err) defer db.Close() // Create test schema schema := ` PRAGMA foreign_keys = ON; CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username VARCHAR(50) NOT NULL UNIQUE, email VARCHAR(100) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, title VARCHAR(200) NOT NULL, content TEXT, published BOOLEAN DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE comments ( id INTEGER PRIMARY KEY AUTOINCREMENT, post_id INTEGER NOT NULL, user_id INTEGER NOT NULL, comment TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE INDEX idx_posts_user_id ON posts(user_id); CREATE INDEX idx_comments_post_id ON comments(post_id); CREATE UNIQUE INDEX idx_users_email ON users(email); CREATE VIEW user_post_count AS SELECT u.id, u.username, COUNT(p.id) as post_count FROM users u LEFT JOIN posts p ON u.id = p.user_id GROUP BY u.id, u.username; ` _, err = db.Exec(schema) require.NoError(t, err) return dbPath } func TestReader_ReadDatabase(t *testing.T) { dbPath := setupTestDatabase(t) defer os.Remove(dbPath) options := &readers.ReaderOptions{ FilePath: dbPath, } reader := NewReader(options) db, err := reader.ReadDatabase() require.NoError(t, err) require.NotNil(t, db) // Check database metadata assert.Equal(t, "test.db", db.Name) assert.Equal(t, models.SqlLiteDatabaseType, db.DatabaseType) assert.Equal(t, "sqlite", db.SourceFormat) assert.NotEmpty(t, db.DatabaseVersion) // Check schemas (SQLite should have a single "main" schema) require.Len(t, db.Schemas, 1) schema := db.Schemas[0] assert.Equal(t, "main", schema.Name) // Check tables assert.Len(t, schema.Tables, 3) tableNames := make([]string, len(schema.Tables)) for i, table := range schema.Tables { tableNames[i] = table.Name } assert.Contains(t, tableNames, "users") assert.Contains(t, tableNames, "posts") assert.Contains(t, tableNames, "comments") // Check views assert.Len(t, schema.Views, 1) assert.Equal(t, "user_post_count", schema.Views[0].Name) assert.NotEmpty(t, schema.Views[0].Definition) } func TestReader_ReadTable_Users(t *testing.T) { dbPath := setupTestDatabase(t) defer os.Remove(dbPath) options := &readers.ReaderOptions{ FilePath: dbPath, } reader := NewReader(options) db, err := reader.ReadDatabase() require.NoError(t, err) require.NotNil(t, db) // Find users table var usersTable *models.Table for _, table := range db.Schemas[0].Tables { if table.Name == "users" { usersTable = table break } } require.NotNil(t, usersTable) assert.Equal(t, "users", usersTable.Name) assert.Equal(t, "main", usersTable.Schema) // Check columns assert.Len(t, usersTable.Columns, 4) // Check id column idCol, exists := usersTable.Columns["id"] require.True(t, exists) assert.Equal(t, "int", idCol.Type) assert.True(t, idCol.IsPrimaryKey) assert.True(t, idCol.AutoIncrement) assert.True(t, idCol.NotNull) // Check username column usernameCol, exists := usersTable.Columns["username"] require.True(t, exists) assert.Equal(t, "string", usernameCol.Type) assert.True(t, usernameCol.NotNull) assert.False(t, usernameCol.IsPrimaryKey) // Check email column emailCol, exists := usersTable.Columns["email"] require.True(t, exists) assert.Equal(t, "string", emailCol.Type) assert.True(t, emailCol.NotNull) // Check primary key constraint assert.Len(t, usersTable.Constraints, 1) pkConstraint, exists := usersTable.Constraints["users_pkey"] require.True(t, exists) assert.Equal(t, models.PrimaryKeyConstraint, pkConstraint.Type) assert.Equal(t, []string{"id"}, pkConstraint.Columns) // Check indexes (should have unique index on email and username) assert.GreaterOrEqual(t, len(usersTable.Indexes), 1) } func TestReader_ReadTable_Posts(t *testing.T) { dbPath := setupTestDatabase(t) defer os.Remove(dbPath) options := &readers.ReaderOptions{ FilePath: dbPath, } reader := NewReader(options) db, err := reader.ReadDatabase() require.NoError(t, err) require.NotNil(t, db) // Find posts table var postsTable *models.Table for _, table := range db.Schemas[0].Tables { if table.Name == "posts" { postsTable = table break } } require.NotNil(t, postsTable) // Check columns assert.Len(t, postsTable.Columns, 6) // Check foreign key constraint hasForeignKey := false for _, constraint := range postsTable.Constraints { if constraint.Type == models.ForeignKeyConstraint { hasForeignKey = true assert.Equal(t, "users", constraint.ReferencedTable) assert.Equal(t, "CASCADE", constraint.OnDelete) } } assert.True(t, hasForeignKey, "Posts table should have a foreign key constraint") // Check relationships assert.GreaterOrEqual(t, len(postsTable.Relationships), 1) // Check indexes hasUserIdIndex := false for _, index := range postsTable.Indexes { if index.Name == "idx_posts_user_id" { hasUserIdIndex = true assert.Contains(t, index.Columns, "user_id") } } assert.True(t, hasUserIdIndex, "Posts table should have idx_posts_user_id index") } func TestReader_ReadTable_Comments(t *testing.T) { dbPath := setupTestDatabase(t) defer os.Remove(dbPath) options := &readers.ReaderOptions{ FilePath: dbPath, } reader := NewReader(options) db, err := reader.ReadDatabase() require.NoError(t, err) require.NotNil(t, db) // Find comments table var commentsTable *models.Table for _, table := range db.Schemas[0].Tables { if table.Name == "comments" { commentsTable = table break } } require.NotNil(t, commentsTable) // Check foreign key constraints (should have 2) fkCount := 0 for _, constraint := range commentsTable.Constraints { if constraint.Type == models.ForeignKeyConstraint { fkCount++ } } assert.Equal(t, 2, fkCount, "Comments table should have 2 foreign key constraints") } func TestReader_ReadSchema(t *testing.T) { dbPath := setupTestDatabase(t) defer os.Remove(dbPath) options := &readers.ReaderOptions{ FilePath: dbPath, } reader := NewReader(options) schema, err := reader.ReadSchema() require.NoError(t, err) require.NotNil(t, schema) assert.Equal(t, "main", schema.Name) assert.Len(t, schema.Tables, 3) assert.Len(t, schema.Views, 1) } func TestReader_ReadTable(t *testing.T) { dbPath := setupTestDatabase(t) defer os.Remove(dbPath) options := &readers.ReaderOptions{ FilePath: dbPath, } reader := NewReader(options) table, err := reader.ReadTable() require.NoError(t, err) require.NotNil(t, table) assert.NotEmpty(t, table.Name) assert.NotEmpty(t, table.Columns) } func TestReader_ConnectionString(t *testing.T) { dbPath := setupTestDatabase(t) defer os.Remove(dbPath) options := &readers.ReaderOptions{ ConnectionString: dbPath, } reader := NewReader(options) db, err := reader.ReadDatabase() require.NoError(t, err) require.NotNil(t, db) assert.Len(t, db.Schemas, 1) } func TestReader_InvalidPath(t *testing.T) { options := &readers.ReaderOptions{ FilePath: "/nonexistent/path/to/database.db", } reader := NewReader(options) _, err := reader.ReadDatabase() assert.Error(t, err) } func TestReader_MissingPath(t *testing.T) { options := &readers.ReaderOptions{} reader := NewReader(options) _, err := reader.ReadDatabase() assert.Error(t, err) assert.Contains(t, err.Error(), "file path or connection string is required") }