hako/internal/server/handlers/system_test.go
2026-01-12 19:33:08 +01:00

406 lines
15 KiB
Go

package handlers
import (
"context"
"encoding/json"
"log/slog"
"net/http"
"os"
"testing"
"git.nakama.town/fmartingr/hako/internal/archival/archiver"
archivalDomain "git.nakama.town/fmartingr/hako/internal/archival/domain"
archivalStore "git.nakama.town/fmartingr/hako/internal/archival/store"
authDomain "git.nakama.town/fmartingr/hako/internal/auth/domain"
"git.nakama.town/fmartingr/hako/internal/database"
"git.nakama.town/fmartingr/hako/internal/extractors"
"git.nakama.town/fmartingr/hako/internal/model"
"git.nakama.town/fmartingr/hako/internal/server/middleware"
"git.nakama.town/fmartingr/hako/internal/testutil"
"github.com/stretchr/testify/require"
)
func TestHandleSystem_NotLoggedIn(t *testing.T) {
// Create temporary database for extractor config store
tmpDB, err := os.CreateTemp("", "hako_test_*.db")
require.NoError(t, err)
defer func() {
_ = tmpDB.Close()
_ = os.Remove(tmpDB.Name())
_ = os.Remove(tmpDB.Name() + "-wal")
_ = os.Remove(tmpDB.Name() + "-shm")
}()
dbURL := "sqlite:" + tmpDB.Name()
dbConnections, err := database.NewConnections(dbURL, dbURL)
require.NoError(t, err)
defer func() {
_ = dbConnections.Close()
}()
// Run migrations
err = database.InitSchema(dbConnections)
require.NoError(t, err)
// Create extractor manager
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}))
extractorMgr := extractors.NewManager(logger)
// Create archiver manager
archiverMgr := archiver.NewManager()
// Create archiver config store
archiverConfigStore := archivalStore.NewArchiverConfigStore(dbConnections.Read, dbConnections.Write)
// Create settings store
settingsStore := archivalStore.NewSettingsStore(dbConnections.Read, dbConnections.Write)
// Create system handler
systemHandler := NewSystemHandler(func(ctx context.Context) (string, error) {
return model.BuildVersion, nil
}, extractorMgr, archiverMgr, archiverConfigStore, settingsStore)
// Perform request without authentication
w := testutil.PerformRequest(systemHandler.HandleSystem, http.MethodGet, "/system")
// Assert response
resp := testutil.NewTestResponse(w)
resp.AssertStatus(t, http.StatusOK)
// Check JSON structure contains version, commit, and date
var jsonData map[string]any
err = json.Unmarshal(resp.GetBody(), &jsonData)
require.NoError(t, err, "Response should be valid JSON")
// Check that version, commit, and date keys exist
versionValue, ok := jsonData["version"]
require.True(t, ok, "Response should contain 'version' key")
require.NotEmpty(t, versionValue, "Version should not be empty")
commitValue, ok := jsonData["commit"]
require.True(t, ok, "Response should contain 'commit' key")
dateValue, ok := jsonData["date"]
require.True(t, ok, "Response should contain 'date' key")
// Verify it's the hako application version (defaults to "dev" in tests)
require.Equal(t, model.BuildVersion, versionValue)
require.Equal(t, model.BuildCommit, commitValue)
require.Equal(t, model.BuildDate, dateValue)
}
func TestHandleSystem_LoggedIn(t *testing.T) {
ctx := context.Background()
deps := testutil.GetTestConfigurationAndDependencies(t, ctx)
// Initialize domains
deps.Dependencies.Domains().SetLinks(archivalDomain.NewLinkDomain(deps.Dependencies))
deps.Dependencies.Domains().SetArchives(archivalDomain.NewArchiveDomain(deps.Dependencies))
deps.Dependencies.Domains().SetCategories(archivalDomain.NewCategoryDomain(deps.Dependencies))
deps.Dependencies.Domains().SetAuth(authDomain.NewAuthDomain(deps.Dependencies))
// Create a test user and get token
_, token, err := testutil.NewTestUser(t, deps.Dependencies, "test@example.com", "testpassword123")
require.NoError(t, err)
// Create extractor manager
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}))
extractorMgr := extractors.NewManager(logger)
// Create archiver manager
archiverMgr := archiver.NewManager()
// Create archiver config store using database connections from deps
archiverConfigStore := archivalStore.NewArchiverConfigStore(deps.DBConnections.Read, deps.DBConnections.Write)
// Create settings store
settingsStore := archivalStore.NewSettingsStore(deps.DBConnections.Read, deps.DBConnections.Write)
// Create system handler
systemHandler := NewSystemHandler(func(ctx context.Context) (string, error) {
return model.BuildVersion, nil
}, extractorMgr, archiverMgr, archiverConfigStore, settingsStore)
// Create handler with global auth middleware (sets context if token is present)
globalAuthMiddleware := middleware.AuthMiddleware(deps.Dependencies.GetJWTService(), deps.Dependencies.UserStore)
handler := globalAuthMiddleware(http.HandlerFunc(systemHandler.HandleSystem))
// Perform authenticated request
w := testutil.PerformRequestWithHandler(handler, http.MethodGet, "/system",
testutil.WithAuthToken(token),
)
// Assert response
resp := testutil.NewTestResponse(w)
resp.AssertStatus(t, http.StatusOK)
// Check JSON structure contains version, commit, and date
var jsonData map[string]any
err = json.Unmarshal(resp.GetBody(), &jsonData)
require.NoError(t, err, "Response should be valid JSON")
// Check that version, commit, and date keys exist
versionValue, ok := jsonData["version"]
require.True(t, ok, "Response should contain 'version' key when logged in")
require.NotEmpty(t, versionValue, "Version should not be empty")
commitValue, ok := jsonData["commit"]
require.True(t, ok, "Response should contain 'commit' key when logged in")
dateValue, ok := jsonData["date"]
require.True(t, ok, "Response should contain 'date' key when logged in")
// Verify it's the hako application version (defaults to "dev" in tests)
require.Equal(t, model.BuildVersion, versionValue)
require.Equal(t, model.BuildCommit, commitValue)
require.Equal(t, model.BuildDate, dateValue)
}
func TestHandleGetRulesConfig(t *testing.T) {
ctx := context.Background()
deps := testutil.GetTestConfigurationAndDependencies(t, ctx)
// Initialize domains
deps.Dependencies.Domains().SetLinks(archivalDomain.NewLinkDomain(deps.Dependencies))
deps.Dependencies.Domains().SetArchives(archivalDomain.NewArchiveDomain(deps.Dependencies))
deps.Dependencies.Domains().SetCategories(archivalDomain.NewCategoryDomain(deps.Dependencies))
deps.Dependencies.Domains().SetAuth(authDomain.NewAuthDomain(deps.Dependencies))
// Create a test user and get token
_, token, err := testutil.NewTestUser(t, deps.Dependencies, "test@example.com", "testpassword123")
require.NoError(t, err)
// Create extractor manager
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}))
extractorMgr := extractors.NewManager(logger)
// Create archiver manager
archiverMgr := archiver.NewManager()
// Create archiver config store
archiverConfigStore := archivalStore.NewArchiverConfigStore(deps.DBConnections.Read, deps.DBConnections.Write)
// Create settings store
settingsStore := archivalStore.NewSettingsStore(deps.DBConnections.Read, deps.DBConnections.Write)
// Create system handler
systemHandler := NewSystemHandler(func(ctx context.Context) (string, error) {
return model.BuildVersion, nil
}, extractorMgr, archiverMgr, archiverConfigStore, settingsStore)
// Create handler with global auth middleware
globalAuthMiddleware := middleware.AuthMiddleware(deps.Dependencies.GetJWTService(), deps.Dependencies.UserStore)
handler := globalAuthMiddleware(http.HandlerFunc(systemHandler.HandleGetRulesConfig))
// Perform authenticated request
w := testutil.PerformRequestWithHandler(handler, http.MethodGet, "/system/rules/config",
testutil.WithAuthToken(token),
)
// Assert response
resp := testutil.NewTestResponse(w)
resp.AssertStatus(t, http.StatusOK)
// Check JSON structure
var jsonData map[string]any
err = json.Unmarshal(resp.GetBody(), &jsonData)
require.NoError(t, err, "Response should be valid JSON")
// Check that rules and default_extractors keys exist
_, ok := jsonData["rules"]
require.True(t, ok, "Response should contain 'rules' key")
_, ok = jsonData["default_extractors"]
require.True(t, ok, "Response should contain 'default_extractors' key")
}
func TestHandleSaveRulesConfig(t *testing.T) {
ctx := context.Background()
deps := testutil.GetTestConfigurationAndDependencies(t, ctx)
// Initialize domains
deps.Dependencies.Domains().SetLinks(archivalDomain.NewLinkDomain(deps.Dependencies))
deps.Dependencies.Domains().SetArchives(archivalDomain.NewArchiveDomain(deps.Dependencies))
deps.Dependencies.Domains().SetCategories(archivalDomain.NewCategoryDomain(deps.Dependencies))
deps.Dependencies.Domains().SetAuth(authDomain.NewAuthDomain(deps.Dependencies))
// Create a test user and get token
_, token, err := testutil.NewTestUser(t, deps.Dependencies, "test@example.com", "testpassword123")
require.NoError(t, err)
// Create extractor manager
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}))
extractorMgr := extractors.NewManager(logger)
// Create archiver manager
archiverMgr := archiver.NewManager()
// Create archiver config store
archiverConfigStore := archivalStore.NewArchiverConfigStore(deps.DBConnections.Read, deps.DBConnections.Write)
// Create settings store
settingsStore := archivalStore.NewSettingsStore(deps.DBConnections.Read, deps.DBConnections.Write)
// Create system handler
systemHandler := NewSystemHandler(func(ctx context.Context) (string, error) {
return model.BuildVersion, nil
}, extractorMgr, archiverMgr, archiverConfigStore, settingsStore)
// Create handler with global auth middleware
globalAuthMiddleware := middleware.AuthMiddleware(deps.Dependencies.GetJWTService(), deps.Dependencies.UserStore)
handler := globalAuthMiddleware(http.HandlerFunc(systemHandler.HandleSaveRulesConfig))
// Valid rules config
validRulesConfig := `{
"rules": [
{
"mimetype": "text/html",
"extractors": [{"key": "obelisk"}]
}
],
"default_extractors": [{"key": "obelisk"}, {"key": "thumbnail"}]
}`
// Perform authenticated request to save rules
w := testutil.PerformRequestWithHandler(handler, http.MethodPut, "/system/rules/config",
testutil.WithAuthToken(token),
testutil.WithBody(validRulesConfig),
testutil.WithHeader("Content-Type", "application/json"),
)
// Assert response
resp := testutil.NewTestResponse(w)
resp.AssertStatus(t, http.StatusOK)
// Check response message
var responseData map[string]string
err = json.Unmarshal(resp.GetBody(), &responseData)
require.NoError(t, err, "Response should be valid JSON")
require.Equal(t, "Rules config saved successfully", responseData["message"])
// Verify rules were saved by getting them back
getHandler := globalAuthMiddleware(http.HandlerFunc(systemHandler.HandleGetRulesConfig))
w = testutil.PerformRequestWithHandler(getHandler, http.MethodGet, "/system/rules/config",
testutil.WithAuthToken(token),
)
resp = testutil.NewTestResponse(w)
resp.AssertStatus(t, http.StatusOK)
var savedConfig map[string]any
err = json.Unmarshal(resp.GetBody(), &savedConfig)
require.NoError(t, err)
// Verify rules were saved
rules, ok := savedConfig["rules"].([]any)
require.True(t, ok, "Rules should be an array")
require.Len(t, rules, 1, "Should have one rule")
}
func TestHandleSaveRulesConfig_InvalidJSON(t *testing.T) {
ctx := context.Background()
deps := testutil.GetTestConfigurationAndDependencies(t, ctx)
// Initialize domains
deps.Dependencies.Domains().SetLinks(archivalDomain.NewLinkDomain(deps.Dependencies))
deps.Dependencies.Domains().SetArchives(archivalDomain.NewArchiveDomain(deps.Dependencies))
deps.Dependencies.Domains().SetCategories(archivalDomain.NewCategoryDomain(deps.Dependencies))
deps.Dependencies.Domains().SetAuth(authDomain.NewAuthDomain(deps.Dependencies))
// Create a test user and get token
_, token, err := testutil.NewTestUser(t, deps.Dependencies, "test@example.com", "testpassword123")
require.NoError(t, err)
// Create extractor manager
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}))
extractorMgr := extractors.NewManager(logger)
// Create archiver manager
archiverMgr := archiver.NewManager()
// Create archiver config store
archiverConfigStore := archivalStore.NewArchiverConfigStore(deps.DBConnections.Read, deps.DBConnections.Write)
// Create settings store
settingsStore := archivalStore.NewSettingsStore(deps.DBConnections.Read, deps.DBConnections.Write)
// Create system handler
systemHandler := NewSystemHandler(func(ctx context.Context) (string, error) {
return model.BuildVersion, nil
}, extractorMgr, archiverMgr, archiverConfigStore, settingsStore)
// Create handler with global auth middleware
globalAuthMiddleware := middleware.AuthMiddleware(deps.Dependencies.GetJWTService(), deps.Dependencies.UserStore)
handler := globalAuthMiddleware(http.HandlerFunc(systemHandler.HandleSaveRulesConfig))
// Invalid JSON
invalidJSON := `{invalid json}`
// Perform authenticated request
w := testutil.PerformRequestWithHandler(handler, http.MethodPut, "/system/rules/config",
testutil.WithAuthToken(token),
testutil.WithBody(invalidJSON),
testutil.WithHeader("Content-Type", "application/json"),
)
// Assert response
resp := testutil.NewTestResponse(w)
resp.AssertStatus(t, http.StatusBadRequest)
}
func TestHandleSaveRulesConfig_InvalidRuleStructure(t *testing.T) {
ctx := context.Background()
deps := testutil.GetTestConfigurationAndDependencies(t, ctx)
// Initialize domains
deps.Dependencies.Domains().SetLinks(archivalDomain.NewLinkDomain(deps.Dependencies))
deps.Dependencies.Domains().SetArchives(archivalDomain.NewArchiveDomain(deps.Dependencies))
deps.Dependencies.Domains().SetCategories(archivalDomain.NewCategoryDomain(deps.Dependencies))
deps.Dependencies.Domains().SetAuth(authDomain.NewAuthDomain(deps.Dependencies))
// Create a test user and get token
_, token, err := testutil.NewTestUser(t, deps.Dependencies, "test@example.com", "testpassword123")
require.NoError(t, err)
// Create extractor manager
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}))
extractorMgr := extractors.NewManager(logger)
// Create archiver manager
archiverMgr := archiver.NewManager()
// Create archiver config store
archiverConfigStore := archivalStore.NewArchiverConfigStore(deps.DBConnections.Read, deps.DBConnections.Write)
// Create settings store
settingsStore := archivalStore.NewSettingsStore(deps.DBConnections.Read, deps.DBConnections.Write)
// Create system handler
systemHandler := NewSystemHandler(func(ctx context.Context) (string, error) {
return model.BuildVersion, nil
}, extractorMgr, archiverMgr, archiverConfigStore, settingsStore)
// Create handler with global auth middleware
globalAuthMiddleware := middleware.AuthMiddleware(deps.Dependencies.GetJWTService(), deps.Dependencies.UserStore)
handler := globalAuthMiddleware(http.HandlerFunc(systemHandler.HandleSaveRulesConfig))
// Valid JSON but invalid rule structure (missing required fields)
invalidRuleConfig := `{
"rules": [
{
"mimetype": ""
}
],
"default_extractors": []
}`
// Perform authenticated request
w := testutil.PerformRequestWithHandler(handler, http.MethodPut, "/system/rules/config",
testutil.WithAuthToken(token),
testutil.WithBody(invalidRuleConfig),
testutil.WithHeader("Content-Type", "application/json"),
)
// Assert response - should fail validation
resp := testutil.NewTestResponse(w)
resp.AssertStatus(t, http.StatusBadRequest)
}