179 lines
4.7 KiB
Go
179 lines
4.7 KiB
Go
// Keywarden - Centralized SSH Key Management and Deployment
|
|
// Copyright (C) 2026 Patrick Asmus (scriptos)
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
//go:build integration
|
|
|
|
package database
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestNewDatabase(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dbPath := filepath.Join(tmpDir, "test.db")
|
|
|
|
db, err := New(dbPath)
|
|
if err != nil {
|
|
t.Fatalf("New() failed: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
// Verify file was created
|
|
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
|
|
t.Fatal("Database file was not created")
|
|
}
|
|
}
|
|
|
|
func TestMigrationsCreateTables(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
db, err := New(filepath.Join(tmpDir, "test.db"))
|
|
if err != nil {
|
|
t.Fatalf("New() failed: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
tables := []string{"users", "ssh_keys", "servers", "key_deployments", "audit_log", "settings", "_migrations"}
|
|
for _, table := range tables {
|
|
var count int
|
|
err := db.QueryRow(`SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?`, table).Scan(&count)
|
|
if err != nil {
|
|
t.Fatalf("Failed to check table %s: %v", table, err)
|
|
}
|
|
if count == 0 {
|
|
t.Fatalf("Table %q was not created", table)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMigrationsIdempotent(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dbPath := filepath.Join(tmpDir, "test.db")
|
|
|
|
// Run migrations twice (opening creates + migrates)
|
|
db1, err := New(dbPath)
|
|
if err != nil {
|
|
t.Fatalf("New() first call failed: %v", err)
|
|
}
|
|
db1.Close()
|
|
|
|
db2, err := New(dbPath)
|
|
if err != nil {
|
|
t.Fatalf("New() second call failed (migrations should be idempotent): %v", err)
|
|
}
|
|
defer db2.Close()
|
|
}
|
|
|
|
func TestAlterMigrationsTracked(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
db, err := New(filepath.Join(tmpDir, "test.db"))
|
|
if err != nil {
|
|
t.Fatalf("New() failed: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
// Check that alter migrations are tracked
|
|
expectedMigrations := []string{"add_mfa_enabled", "add_mfa_secret", "add_is_master_key"}
|
|
for _, name := range expectedMigrations {
|
|
var count int
|
|
err := db.QueryRow(`SELECT COUNT(*) FROM _migrations WHERE name = ?`, name).Scan(&count)
|
|
if err != nil {
|
|
t.Fatalf("Failed to check migration %s: %v", name, err)
|
|
}
|
|
if count == 0 {
|
|
t.Fatalf("Migration %q was not tracked", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInsertAndQueryUser(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
db, err := New(filepath.Join(tmpDir, "test.db"))
|
|
if err != nil {
|
|
t.Fatalf("New() failed: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
result, err := db.Exec(
|
|
`INSERT INTO users (username, email, password_hash, role) VALUES (?, ?, ?, ?)`,
|
|
"testuser", "test@example.com", "hash123", "user",
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Insert user failed: %v", err)
|
|
}
|
|
|
|
id, _ := result.LastInsertId()
|
|
if id == 0 {
|
|
t.Fatal("Expected non-zero insert ID")
|
|
}
|
|
|
|
var username string
|
|
err = db.QueryRow(`SELECT username FROM users WHERE id = ?`, id).Scan(&username)
|
|
if err != nil {
|
|
t.Fatalf("Query user failed: %v", err)
|
|
}
|
|
if username != "testuser" {
|
|
t.Fatalf("Expected username 'testuser', got %q", username)
|
|
}
|
|
}
|
|
|
|
func TestSSHKeyWithMasterFlag(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
db, err := New(filepath.Join(tmpDir, "test.db"))
|
|
if err != nil {
|
|
t.Fatalf("New() failed: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
// Create a user first
|
|
db.Exec(`INSERT INTO users (username, email, password_hash, role) VALUES (?, ?, ?, ?)`,
|
|
"testuser", "test@example.com", "hash", "user")
|
|
|
|
// Insert a master key
|
|
_, err = db.Exec(
|
|
`INSERT INTO ssh_keys (user_id, name, key_type, bits, fingerprint, public_key, private_key_enc, is_master)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1, "Master Key", "ed25519", 256, "SHA256:test", "pubkey", "encpriv", 1,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Insert master key failed: %v", err)
|
|
}
|
|
|
|
// Verify is_master flag
|
|
var isMaster int
|
|
err = db.QueryRow(`SELECT is_master FROM ssh_keys WHERE user_id = 1 AND is_master = 1`).Scan(&isMaster)
|
|
if err != nil {
|
|
t.Fatalf("Query master key failed: %v", err)
|
|
}
|
|
if isMaster != 1 {
|
|
t.Fatalf("Expected is_master=1, got %d", isMaster)
|
|
}
|
|
}
|
|
|
|
func TestForeignKeyCascade(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
db, err := New(filepath.Join(tmpDir, "test.db"))
|
|
if err != nil {
|
|
t.Fatalf("New() failed: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
// Insert user and key
|
|
db.Exec(`INSERT INTO users (username, email, password_hash, role) VALUES (?, ?, ?, ?)`,
|
|
"testuser", "test@example.com", "hash", "user")
|
|
db.Exec(`INSERT INTO ssh_keys (user_id, name, key_type, bits, fingerprint, public_key, private_key_enc)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)`, 1, "Test", "ed25519", 256, "fp", "pub", "priv")
|
|
|
|
// Delete user — key should cascade
|
|
db.Exec(`DELETE FROM users WHERE id = 1`)
|
|
|
|
var count int
|
|
db.QueryRow(`SELECT COUNT(*) FROM ssh_keys WHERE user_id = 1`).Scan(&count)
|
|
if count != 0 {
|
|
t.Fatalf("Expected 0 keys after user deletion (cascade), got %d", count)
|
|
}
|
|
}
|