Release: v0.1.0-alpha
Release Docker Image / Build & Push Docker Image (release) Failing after 1m30s
Release Docker Image / Build & Push Docker Image (release) Failing after 1m30s
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user