- testFileRange: test GetFileWithRange with various offset/limit values - testDeclarationsWithMetadata: test file metadata in declarations - Verify byte_size and line_count are computed correctly - Test edge cases: offset beyond EOF, non-indexed files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
703 lines
20 KiB
Go
703 lines
20 KiB
Go
package database
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// runStoreTests runs the test suite against a Store implementation.
|
|
func runStoreTests(t *testing.T, newStore func(t *testing.T) Store) {
|
|
tests := []struct {
|
|
name string
|
|
test func(t *testing.T, store Store)
|
|
}{
|
|
{"Initialize", testInitialize},
|
|
{"Revisions", testRevisions},
|
|
{"Options", testOptions},
|
|
{"OptionsSearch", testOptionsSearch},
|
|
{"OptionChildren", testOptionChildren},
|
|
{"Declarations", testDeclarations},
|
|
{"Files", testFiles},
|
|
{"FileRange", testFileRange},
|
|
{"DeclarationsWithMetadata", testDeclarationsWithMetadata},
|
|
{"SchemaVersion", testSchemaVersion},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
store := newStore(t)
|
|
defer store.Close()
|
|
tt.test(t, store)
|
|
})
|
|
}
|
|
}
|
|
|
|
func testInitialize(t *testing.T, store Store) {
|
|
ctx := context.Background()
|
|
|
|
// Initialize should work
|
|
if err := store.Initialize(ctx); err != nil {
|
|
t.Fatalf("Initialize failed: %v", err)
|
|
}
|
|
|
|
// Initialize again should be idempotent
|
|
if err := store.Initialize(ctx); err != nil {
|
|
t.Fatalf("Second Initialize failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func testRevisions(t *testing.T, store Store) {
|
|
ctx := context.Background()
|
|
if err := store.Initialize(ctx); err != nil {
|
|
t.Fatalf("Initialize failed: %v", err)
|
|
}
|
|
|
|
// Create a revision
|
|
rev := &Revision{
|
|
GitHash: "abc123def456",
|
|
ChannelName: "nixos-unstable",
|
|
CommitDate: time.Now().UTC().Truncate(time.Second),
|
|
OptionCount: 0,
|
|
}
|
|
|
|
if err := store.CreateRevision(ctx, rev); err != nil {
|
|
t.Fatalf("CreateRevision failed: %v", err)
|
|
}
|
|
|
|
if rev.ID == 0 {
|
|
t.Error("Expected revision ID to be set")
|
|
}
|
|
|
|
// Get by git hash
|
|
got, err := store.GetRevision(ctx, "abc123def456")
|
|
if err != nil {
|
|
t.Fatalf("GetRevision failed: %v", err)
|
|
}
|
|
if got == nil {
|
|
t.Fatal("Expected revision, got nil")
|
|
}
|
|
if got.GitHash != rev.GitHash {
|
|
t.Errorf("GitHash = %q, want %q", got.GitHash, rev.GitHash)
|
|
}
|
|
if got.ChannelName != rev.ChannelName {
|
|
t.Errorf("ChannelName = %q, want %q", got.ChannelName, rev.ChannelName)
|
|
}
|
|
|
|
// Get by channel name
|
|
got, err = store.GetRevisionByChannel(ctx, "nixos-unstable")
|
|
if err != nil {
|
|
t.Fatalf("GetRevisionByChannel failed: %v", err)
|
|
}
|
|
if got == nil {
|
|
t.Fatal("Expected revision, got nil")
|
|
}
|
|
if got.ID != rev.ID {
|
|
t.Errorf("ID = %d, want %d", got.ID, rev.ID)
|
|
}
|
|
|
|
// Get non-existent
|
|
got, err = store.GetRevision(ctx, "nonexistent")
|
|
if err != nil {
|
|
t.Fatalf("GetRevision for nonexistent failed: %v", err)
|
|
}
|
|
if got != nil {
|
|
t.Error("Expected nil for nonexistent revision")
|
|
}
|
|
|
|
// List revisions
|
|
revs, err := store.ListRevisions(ctx)
|
|
if err != nil {
|
|
t.Fatalf("ListRevisions failed: %v", err)
|
|
}
|
|
if len(revs) != 1 {
|
|
t.Errorf("Expected 1 revision, got %d", len(revs))
|
|
}
|
|
|
|
// Update option count
|
|
if err := store.UpdateRevisionOptionCount(ctx, rev.ID, 100); err != nil {
|
|
t.Fatalf("UpdateRevisionOptionCount failed: %v", err)
|
|
}
|
|
got, _ = store.GetRevision(ctx, "abc123def456")
|
|
if got.OptionCount != 100 {
|
|
t.Errorf("OptionCount = %d, want 100", got.OptionCount)
|
|
}
|
|
|
|
// Delete revision
|
|
if err := store.DeleteRevision(ctx, rev.ID); err != nil {
|
|
t.Fatalf("DeleteRevision failed: %v", err)
|
|
}
|
|
|
|
revs, _ = store.ListRevisions(ctx)
|
|
if len(revs) != 0 {
|
|
t.Errorf("Expected 0 revisions after delete, got %d", len(revs))
|
|
}
|
|
}
|
|
|
|
func testOptions(t *testing.T, store Store) {
|
|
ctx := context.Background()
|
|
if err := store.Initialize(ctx); err != nil {
|
|
t.Fatalf("Initialize failed: %v", err)
|
|
}
|
|
|
|
// Create a revision first
|
|
rev := &Revision{GitHash: "test123", ChannelName: "test"}
|
|
if err := store.CreateRevision(ctx, rev); err != nil {
|
|
t.Fatalf("CreateRevision failed: %v", err)
|
|
}
|
|
|
|
// Create an option
|
|
opt := &Option{
|
|
RevisionID: rev.ID,
|
|
Name: "services.nginx.enable",
|
|
ParentPath: "services.nginx",
|
|
Type: "boolean",
|
|
DefaultValue: "false",
|
|
Description: "Whether to enable nginx.",
|
|
ReadOnly: false,
|
|
}
|
|
|
|
if err := store.CreateOption(ctx, opt); err != nil {
|
|
t.Fatalf("CreateOption failed: %v", err)
|
|
}
|
|
|
|
if opt.ID == 0 {
|
|
t.Error("Expected option ID to be set")
|
|
}
|
|
|
|
// Get option
|
|
got, err := store.GetOption(ctx, rev.ID, "services.nginx.enable")
|
|
if err != nil {
|
|
t.Fatalf("GetOption failed: %v", err)
|
|
}
|
|
if got == nil {
|
|
t.Fatal("Expected option, got nil")
|
|
}
|
|
if got.Name != opt.Name {
|
|
t.Errorf("Name = %q, want %q", got.Name, opt.Name)
|
|
}
|
|
if got.Type != opt.Type {
|
|
t.Errorf("Type = %q, want %q", got.Type, opt.Type)
|
|
}
|
|
if got.Description != opt.Description {
|
|
t.Errorf("Description = %q, want %q", got.Description, opt.Description)
|
|
}
|
|
|
|
// Get non-existent option
|
|
got, err = store.GetOption(ctx, rev.ID, "nonexistent")
|
|
if err != nil {
|
|
t.Fatalf("GetOption for nonexistent failed: %v", err)
|
|
}
|
|
if got != nil {
|
|
t.Error("Expected nil for nonexistent option")
|
|
}
|
|
|
|
// Batch create options
|
|
opts := []*Option{
|
|
{RevisionID: rev.ID, Name: "services.caddy.enable", ParentPath: "services.caddy", Type: "boolean"},
|
|
{RevisionID: rev.ID, Name: "services.caddy.config", ParentPath: "services.caddy", Type: "string"},
|
|
}
|
|
if err := store.CreateOptionsBatch(ctx, opts); err != nil {
|
|
t.Fatalf("CreateOptionsBatch failed: %v", err)
|
|
}
|
|
|
|
for _, o := range opts {
|
|
if o.ID == 0 {
|
|
t.Errorf("Expected option %q ID to be set", o.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testOptionsSearch(t *testing.T, store Store) {
|
|
ctx := context.Background()
|
|
if err := store.Initialize(ctx); err != nil {
|
|
t.Fatalf("Initialize failed: %v", err)
|
|
}
|
|
|
|
rev := &Revision{GitHash: "search123", ChannelName: "test"}
|
|
if err := store.CreateRevision(ctx, rev); err != nil {
|
|
t.Fatalf("CreateRevision failed: %v", err)
|
|
}
|
|
|
|
opts := []*Option{
|
|
{RevisionID: rev.ID, Name: "services.nginx.enable", ParentPath: "services.nginx", Type: "boolean", Description: "Enable the nginx web server"},
|
|
{RevisionID: rev.ID, Name: "services.nginx.package", ParentPath: "services.nginx", Type: "package", Description: "Nginx package to use"},
|
|
{RevisionID: rev.ID, Name: "services.caddy.enable", ParentPath: "services.caddy", Type: "boolean", Description: "Enable the caddy web server"},
|
|
{RevisionID: rev.ID, Name: "programs.git.enable", ParentPath: "programs.git", Type: "boolean", Description: "Enable git version control"},
|
|
}
|
|
if err := store.CreateOptionsBatch(ctx, opts); err != nil {
|
|
t.Fatalf("CreateOptionsBatch failed: %v", err)
|
|
}
|
|
|
|
// Search for "nginx"
|
|
results, err := store.SearchOptions(ctx, rev.ID, "nginx", SearchFilters{})
|
|
if err != nil {
|
|
t.Fatalf("SearchOptions failed: %v", err)
|
|
}
|
|
if len(results) < 1 {
|
|
t.Errorf("Expected at least 1 result for 'nginx', got %d", len(results))
|
|
}
|
|
|
|
// Search with namespace filter
|
|
results, err = store.SearchOptions(ctx, rev.ID, "enable", SearchFilters{Namespace: "services"})
|
|
if err != nil {
|
|
t.Fatalf("SearchOptions with namespace failed: %v", err)
|
|
}
|
|
for _, r := range results {
|
|
if r.Name[:8] != "services" {
|
|
t.Errorf("Result %q doesn't match namespace filter", r.Name)
|
|
}
|
|
}
|
|
|
|
// Search with type filter
|
|
results, err = store.SearchOptions(ctx, rev.ID, "nginx", SearchFilters{Type: "boolean"})
|
|
if err != nil {
|
|
t.Fatalf("SearchOptions with type failed: %v", err)
|
|
}
|
|
for _, r := range results {
|
|
if r.Type != "boolean" {
|
|
t.Errorf("Result %q has type %q, expected boolean", r.Name, r.Type)
|
|
}
|
|
}
|
|
|
|
// Search with limit
|
|
results, err = store.SearchOptions(ctx, rev.ID, "enable", SearchFilters{Limit: 2})
|
|
if err != nil {
|
|
t.Fatalf("SearchOptions with limit failed: %v", err)
|
|
}
|
|
if len(results) > 2 {
|
|
t.Errorf("Expected at most 2 results, got %d", len(results))
|
|
}
|
|
|
|
// Search with special characters (dots) - should not cause syntax errors
|
|
results, err = store.SearchOptions(ctx, rev.ID, "services.nginx", SearchFilters{})
|
|
if err != nil {
|
|
t.Fatalf("SearchOptions with dots failed: %v", err)
|
|
}
|
|
if len(results) < 1 {
|
|
t.Errorf("Expected at least 1 result for 'services.nginx', got %d", len(results))
|
|
}
|
|
|
|
// Search with other special characters
|
|
specialQueries := []string{
|
|
"services.nginx.enable",
|
|
"nginx:package",
|
|
"web-server",
|
|
"(nginx)",
|
|
}
|
|
for _, q := range specialQueries {
|
|
_, err = store.SearchOptions(ctx, rev.ID, q, SearchFilters{})
|
|
if err != nil {
|
|
t.Errorf("SearchOptions with special query %q failed: %v", q, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testOptionChildren(t *testing.T, store Store) {
|
|
ctx := context.Background()
|
|
if err := store.Initialize(ctx); err != nil {
|
|
t.Fatalf("Initialize failed: %v", err)
|
|
}
|
|
|
|
rev := &Revision{GitHash: "children123", ChannelName: "test"}
|
|
if err := store.CreateRevision(ctx, rev); err != nil {
|
|
t.Fatalf("CreateRevision failed: %v", err)
|
|
}
|
|
|
|
// Create a hierarchy of options
|
|
opts := []*Option{
|
|
{RevisionID: rev.ID, Name: "services", ParentPath: "", Type: "attrsOf"},
|
|
{RevisionID: rev.ID, Name: "services.nginx", ParentPath: "services", Type: "submodule"},
|
|
{RevisionID: rev.ID, Name: "services.nginx.enable", ParentPath: "services.nginx", Type: "boolean"},
|
|
{RevisionID: rev.ID, Name: "services.nginx.package", ParentPath: "services.nginx", Type: "package"},
|
|
{RevisionID: rev.ID, Name: "services.caddy", ParentPath: "services", Type: "submodule"},
|
|
}
|
|
if err := store.CreateOptionsBatch(ctx, opts); err != nil {
|
|
t.Fatalf("CreateOptionsBatch failed: %v", err)
|
|
}
|
|
|
|
// Get children of root
|
|
children, err := store.GetChildren(ctx, rev.ID, "")
|
|
if err != nil {
|
|
t.Fatalf("GetChildren root failed: %v", err)
|
|
}
|
|
if len(children) != 1 {
|
|
t.Errorf("Expected 1 root child, got %d", len(children))
|
|
}
|
|
if len(children) > 0 && children[0].Name != "services" {
|
|
t.Errorf("Expected root child to be 'services', got %q", children[0].Name)
|
|
}
|
|
|
|
// Get children of services
|
|
children, err = store.GetChildren(ctx, rev.ID, "services")
|
|
if err != nil {
|
|
t.Fatalf("GetChildren services failed: %v", err)
|
|
}
|
|
if len(children) != 2 {
|
|
t.Errorf("Expected 2 children of services, got %d", len(children))
|
|
}
|
|
|
|
// Get children of services.nginx
|
|
children, err = store.GetChildren(ctx, rev.ID, "services.nginx")
|
|
if err != nil {
|
|
t.Fatalf("GetChildren services.nginx failed: %v", err)
|
|
}
|
|
if len(children) != 2 {
|
|
t.Errorf("Expected 2 children of services.nginx, got %d", len(children))
|
|
}
|
|
}
|
|
|
|
func testDeclarations(t *testing.T, store Store) {
|
|
ctx := context.Background()
|
|
if err := store.Initialize(ctx); err != nil {
|
|
t.Fatalf("Initialize failed: %v", err)
|
|
}
|
|
|
|
rev := &Revision{GitHash: "decl123", ChannelName: "test"}
|
|
if err := store.CreateRevision(ctx, rev); err != nil {
|
|
t.Fatalf("CreateRevision failed: %v", err)
|
|
}
|
|
|
|
opt := &Option{
|
|
RevisionID: rev.ID,
|
|
Name: "services.nginx.enable",
|
|
ParentPath: "services.nginx",
|
|
Type: "boolean",
|
|
}
|
|
if err := store.CreateOption(ctx, opt); err != nil {
|
|
t.Fatalf("CreateOption failed: %v", err)
|
|
}
|
|
|
|
// Create declarations
|
|
decl := &Declaration{
|
|
OptionID: opt.ID,
|
|
FilePath: "nixos/modules/services/web-servers/nginx/default.nix",
|
|
Line: 42,
|
|
}
|
|
if err := store.CreateDeclaration(ctx, decl); err != nil {
|
|
t.Fatalf("CreateDeclaration failed: %v", err)
|
|
}
|
|
|
|
if decl.ID == 0 {
|
|
t.Error("Expected declaration ID to be set")
|
|
}
|
|
|
|
// Get declarations
|
|
decls, err := store.GetDeclarations(ctx, opt.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetDeclarations failed: %v", err)
|
|
}
|
|
if len(decls) != 1 {
|
|
t.Fatalf("Expected 1 declaration, got %d", len(decls))
|
|
}
|
|
if decls[0].FilePath != decl.FilePath {
|
|
t.Errorf("FilePath = %q, want %q", decls[0].FilePath, decl.FilePath)
|
|
}
|
|
if decls[0].Line != 42 {
|
|
t.Errorf("Line = %d, want 42", decls[0].Line)
|
|
}
|
|
|
|
// Batch create
|
|
batch := []*Declaration{
|
|
{OptionID: opt.ID, FilePath: "file1.nix", Line: 10},
|
|
{OptionID: opt.ID, FilePath: "file2.nix", Line: 20},
|
|
}
|
|
if err := store.CreateDeclarationsBatch(ctx, batch); err != nil {
|
|
t.Fatalf("CreateDeclarationsBatch failed: %v", err)
|
|
}
|
|
|
|
decls, _ = store.GetDeclarations(ctx, opt.ID)
|
|
if len(decls) != 3 {
|
|
t.Errorf("Expected 3 declarations after batch, got %d", len(decls))
|
|
}
|
|
}
|
|
|
|
func testFiles(t *testing.T, store Store) {
|
|
ctx := context.Background()
|
|
if err := store.Initialize(ctx); err != nil {
|
|
t.Fatalf("Initialize failed: %v", err)
|
|
}
|
|
|
|
rev := &Revision{GitHash: "files123", ChannelName: "test"}
|
|
if err := store.CreateRevision(ctx, rev); err != nil {
|
|
t.Fatalf("CreateRevision failed: %v", err)
|
|
}
|
|
|
|
// Create a file
|
|
file := &File{
|
|
RevisionID: rev.ID,
|
|
FilePath: "nixos/modules/services/web-servers/nginx/default.nix",
|
|
Extension: ".nix",
|
|
Content: "{ config, lib, pkgs, ... }:\n\n# nginx module\n",
|
|
}
|
|
if err := store.CreateFile(ctx, file); err != nil {
|
|
t.Fatalf("CreateFile failed: %v", err)
|
|
}
|
|
|
|
if file.ID == 0 {
|
|
t.Error("Expected file ID to be set")
|
|
}
|
|
|
|
// Get file
|
|
got, err := store.GetFile(ctx, rev.ID, "nixos/modules/services/web-servers/nginx/default.nix")
|
|
if err != nil {
|
|
t.Fatalf("GetFile failed: %v", err)
|
|
}
|
|
if got == nil {
|
|
t.Fatal("Expected file, got nil")
|
|
}
|
|
if got.Content != file.Content {
|
|
t.Errorf("Content mismatch")
|
|
}
|
|
if got.Extension != ".nix" {
|
|
t.Errorf("Extension = %q, want .nix", got.Extension)
|
|
}
|
|
|
|
// Verify file metadata was computed
|
|
if got.ByteSize != len(file.Content) {
|
|
t.Errorf("ByteSize = %d, want %d", got.ByteSize, len(file.Content))
|
|
}
|
|
if got.LineCount != 3 {
|
|
t.Errorf("LineCount = %d, want 3", got.LineCount)
|
|
}
|
|
|
|
// Get non-existent file
|
|
got, err = store.GetFile(ctx, rev.ID, "nonexistent.nix")
|
|
if err != nil {
|
|
t.Fatalf("GetFile for nonexistent failed: %v", err)
|
|
}
|
|
if got != nil {
|
|
t.Error("Expected nil for nonexistent file")
|
|
}
|
|
|
|
// Batch create
|
|
files := []*File{
|
|
{RevisionID: rev.ID, FilePath: "file1.nix", Extension: ".nix", Content: "content1"},
|
|
{RevisionID: rev.ID, FilePath: "file2.nix", Extension: ".nix", Content: "content2"},
|
|
}
|
|
if err := store.CreateFilesBatch(ctx, files); err != nil {
|
|
t.Fatalf("CreateFilesBatch failed: %v", err)
|
|
}
|
|
|
|
for _, f := range files {
|
|
if f.ID == 0 {
|
|
t.Errorf("Expected file %q ID to be set", f.FilePath)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testFileRange(t *testing.T, store Store) {
|
|
ctx := context.Background()
|
|
if err := store.Initialize(ctx); err != nil {
|
|
t.Fatalf("Initialize failed: %v", err)
|
|
}
|
|
|
|
rev := &Revision{GitHash: "range123", ChannelName: "test"}
|
|
if err := store.CreateRevision(ctx, rev); err != nil {
|
|
t.Fatalf("CreateRevision failed: %v", err)
|
|
}
|
|
|
|
// Create a multi-line file
|
|
content := "line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\nline 9\nline 10"
|
|
file := &File{
|
|
RevisionID: rev.ID,
|
|
FilePath: "multiline.nix",
|
|
Extension: ".nix",
|
|
Content: content,
|
|
}
|
|
if err := store.CreateFile(ctx, file); err != nil {
|
|
t.Fatalf("CreateFile failed: %v", err)
|
|
}
|
|
|
|
// Test default range (first 250 lines, but we have less)
|
|
result, err := store.GetFileWithRange(ctx, rev.ID, "multiline.nix", FileRange{})
|
|
if err != nil {
|
|
t.Fatalf("GetFileWithRange default failed: %v", err)
|
|
}
|
|
if result == nil {
|
|
t.Fatal("Expected result, got nil")
|
|
}
|
|
if result.TotalLines != 10 {
|
|
t.Errorf("TotalLines = %d, want 10", result.TotalLines)
|
|
}
|
|
if result.StartLine != 1 {
|
|
t.Errorf("StartLine = %d, want 1", result.StartLine)
|
|
}
|
|
if result.EndLine != 10 {
|
|
t.Errorf("EndLine = %d, want 10", result.EndLine)
|
|
}
|
|
|
|
// Test with offset
|
|
result, err = store.GetFileWithRange(ctx, rev.ID, "multiline.nix", FileRange{Offset: 2, Limit: 3})
|
|
if err != nil {
|
|
t.Fatalf("GetFileWithRange with offset failed: %v", err)
|
|
}
|
|
if result.StartLine != 3 {
|
|
t.Errorf("StartLine = %d, want 3", result.StartLine)
|
|
}
|
|
if result.EndLine != 5 {
|
|
t.Errorf("EndLine = %d, want 5", result.EndLine)
|
|
}
|
|
if result.Content != "line 3\nline 4\nline 5" {
|
|
t.Errorf("Content = %q, want lines 3-5", result.Content)
|
|
}
|
|
|
|
// Test offset beyond file
|
|
result, err = store.GetFileWithRange(ctx, rev.ID, "multiline.nix", FileRange{Offset: 100})
|
|
if err != nil {
|
|
t.Fatalf("GetFileWithRange beyond end failed: %v", err)
|
|
}
|
|
if result.StartLine != 0 {
|
|
t.Errorf("StartLine = %d, want 0 for beyond end", result.StartLine)
|
|
}
|
|
if result.Content != "" {
|
|
t.Errorf("Content = %q, want empty for beyond end", result.Content)
|
|
}
|
|
|
|
// Test non-existent file
|
|
result, err = store.GetFileWithRange(ctx, rev.ID, "nonexistent.nix", FileRange{})
|
|
if err != nil {
|
|
t.Fatalf("GetFileWithRange for nonexistent failed: %v", err)
|
|
}
|
|
if result != nil {
|
|
t.Error("Expected nil for nonexistent file")
|
|
}
|
|
}
|
|
|
|
func testDeclarationsWithMetadata(t *testing.T, store Store) {
|
|
ctx := context.Background()
|
|
if err := store.Initialize(ctx); err != nil {
|
|
t.Fatalf("Initialize failed: %v", err)
|
|
}
|
|
|
|
rev := &Revision{GitHash: "metadata123", ChannelName: "test"}
|
|
if err := store.CreateRevision(ctx, rev); err != nil {
|
|
t.Fatalf("CreateRevision failed: %v", err)
|
|
}
|
|
|
|
// Create a file
|
|
file := &File{
|
|
RevisionID: rev.ID,
|
|
FilePath: "modules/nginx.nix",
|
|
Extension: ".nix",
|
|
Content: "line 1\nline 2\nline 3",
|
|
}
|
|
if err := store.CreateFile(ctx, file); err != nil {
|
|
t.Fatalf("CreateFile failed: %v", err)
|
|
}
|
|
|
|
// Create an option with declarations
|
|
opt := &Option{
|
|
RevisionID: rev.ID,
|
|
Name: "services.nginx.enable",
|
|
ParentPath: "services.nginx",
|
|
Type: "boolean",
|
|
}
|
|
if err := store.CreateOption(ctx, opt); err != nil {
|
|
t.Fatalf("CreateOption failed: %v", err)
|
|
}
|
|
|
|
// Create declarations - one pointing to indexed file, one to non-indexed
|
|
decls := []*Declaration{
|
|
{OptionID: opt.ID, FilePath: "modules/nginx.nix", Line: 10},
|
|
{OptionID: opt.ID, FilePath: "modules/other.nix", Line: 20},
|
|
}
|
|
if err := store.CreateDeclarationsBatch(ctx, decls); err != nil {
|
|
t.Fatalf("CreateDeclarationsBatch failed: %v", err)
|
|
}
|
|
|
|
// Get declarations with metadata
|
|
declMetas, err := store.GetDeclarationsWithMetadata(ctx, rev.ID, opt.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetDeclarationsWithMetadata failed: %v", err)
|
|
}
|
|
if len(declMetas) != 2 {
|
|
t.Fatalf("Expected 2 declarations, got %d", len(declMetas))
|
|
}
|
|
|
|
// Find the declaration for the indexed file
|
|
var indexed, notIndexed *DeclarationWithMetadata
|
|
for _, d := range declMetas {
|
|
if d.FilePath == "modules/nginx.nix" {
|
|
indexed = d
|
|
} else {
|
|
notIndexed = d
|
|
}
|
|
}
|
|
|
|
if indexed == nil {
|
|
t.Fatal("Expected indexed declaration")
|
|
}
|
|
if !indexed.HasFile {
|
|
t.Error("Expected HasFile=true for indexed file")
|
|
}
|
|
if indexed.ByteSize != len(file.Content) {
|
|
t.Errorf("ByteSize = %d, want %d", indexed.ByteSize, len(file.Content))
|
|
}
|
|
if indexed.LineCount != 3 {
|
|
t.Errorf("LineCount = %d, want 3", indexed.LineCount)
|
|
}
|
|
|
|
if notIndexed == nil {
|
|
t.Fatal("Expected not-indexed declaration")
|
|
}
|
|
if notIndexed.HasFile {
|
|
t.Error("Expected HasFile=false for non-indexed file")
|
|
}
|
|
if notIndexed.ByteSize != 0 {
|
|
t.Errorf("ByteSize = %d, want 0 for non-indexed", notIndexed.ByteSize)
|
|
}
|
|
}
|
|
|
|
func testSchemaVersion(t *testing.T, store Store) {
|
|
ctx := context.Background()
|
|
|
|
// First initialization
|
|
if err := store.Initialize(ctx); err != nil {
|
|
t.Fatalf("First Initialize failed: %v", err)
|
|
}
|
|
|
|
// Create some data
|
|
rev := &Revision{GitHash: "version123", ChannelName: "test"}
|
|
if err := store.CreateRevision(ctx, rev); err != nil {
|
|
t.Fatalf("CreateRevision failed: %v", err)
|
|
}
|
|
|
|
// Second initialization should preserve data (same version)
|
|
if err := store.Initialize(ctx); err != nil {
|
|
t.Fatalf("Second Initialize failed: %v", err)
|
|
}
|
|
|
|
got, err := store.GetRevision(ctx, "version123")
|
|
if err != nil {
|
|
t.Fatalf("GetRevision after second init failed: %v", err)
|
|
}
|
|
if got == nil {
|
|
t.Error("Data should be preserved after second initialization")
|
|
}
|
|
}
|
|
|
|
// TestParentPath tests the ParentPath helper function.
|
|
func TestParentPath(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expect string
|
|
}{
|
|
{"top level", "services", ""},
|
|
{"one level", "services.nginx", "services"},
|
|
{"two levels", "services.nginx.enable", "services.nginx"},
|
|
{"three levels", "services.nginx.virtualHosts.default", "services.nginx.virtualHosts"},
|
|
{"empty", "", ""},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := ParentPath(tt.input)
|
|
if got != tt.expect {
|
|
t.Errorf("ParentPath(%q) = %q, want %q", tt.input, got, tt.expect)
|
|
}
|
|
})
|
|
}
|
|
}
|