Files
Owen f3a959334f Rename project from inventree to vibetree
- go.mod module path (and every internal/vibetree import) inventree -> vibetree
- cmd/inventree/ -> cmd/vibetree/, deploy/inventree.service -> vibetree.service
- Default site name (seed data + the siteName template func's fallback)
  is now "vibetree", shown in the navbar, <title>, and login page
- Session cookie renamed inventree_session -> vibetree_session
- Default DB path data/inventree.db -> data/vibetree.db, CLI/log text,
  Dockerfile, docker-compose.yml, README, .gitignore/.dockerignore all
  updated to match

Existing installs: this changes the session cookie name (users will be
signed out once) and the default DB filename (point -db at your existing
file, or rename it, to keep your data).
2026-08-17 17:14:52 -05:00

203 lines
5.7 KiB
Go

package assets_test
import (
"bytes"
"context"
"database/sql"
"path/filepath"
"strings"
"testing"
"vibetree/internal/assets"
"vibetree/internal/database"
"vibetree/internal/folders"
"vibetree/internal/models"
"vibetree/internal/settings"
)
// Seed data from migrations/0002_seed.sql gives us stable IDs to rely on:
// item types 1=Laptop 2=Desktop 3=Server 4=Switch ... statuses 1=In Use 2=Spare ...
const (
itemTypeLaptop = 1
itemTypeSwitch = 4
statusInUse = 1
statusSpare = 2
)
func testStores(t *testing.T) (*assets.Store, *folders.Store, *sql.DB) {
t.Helper()
dir := t.TempDir()
db, err := database.Open(filepath.Join(dir, "test.db"))
if err != nil {
t.Fatalf("open db: %v", err)
}
t.Cleanup(func() { db.Close() })
fs := folders.NewStore(db)
st := settings.NewStore(db)
return assets.NewStore(db, st, fs), fs, db
}
func TestAssetCreateAutoGeneratesID(t *testing.T) {
ctx := context.Background()
as, _, _ := testStores(t)
a1 := &models.Asset{Name: "Core Switch", ItemTypeID: itemTypeSwitch, StatusID: statusInUse}
if err := as.Create(ctx, a1, nil); err != nil {
t.Fatalf("create: %v", err)
}
if a1.AssetTag != "IT-000001" {
t.Fatalf("expected IT-000001, got %s", a1.AssetTag)
}
a2 := &models.Asset{Name: "Firewall", ItemTypeID: itemTypeSwitch, StatusID: statusInUse}
if err := as.Create(ctx, a2, nil); err != nil {
t.Fatalf("create: %v", err)
}
if a2.AssetTag != "IT-000002" {
t.Fatalf("expected IT-000002, got %s", a2.AssetTag)
}
}
func TestAssetCreateWithManualIDAndCollision(t *testing.T) {
ctx := context.Background()
as, _, _ := testStores(t)
a1 := &models.Asset{AssetTag: "CUSTOM-1", Name: "Core Switch", ItemTypeID: itemTypeSwitch, StatusID: statusInUse}
if err := as.Create(ctx, a1, nil); err != nil {
t.Fatalf("create: %v", err)
}
dup := &models.Asset{AssetTag: "CUSTOM-1", Name: "Other", ItemTypeID: itemTypeSwitch, StatusID: statusInUse}
if err := as.Create(ctx, dup, nil); err != assets.ErrTagTaken {
t.Fatalf("expected ErrTagTaken, got %v", err)
}
}
func TestAssetDuplicateGeneratesNewID(t *testing.T) {
ctx := context.Background()
as, _, _ := testStores(t)
orig := &models.Asset{Name: "Laptop 1", ItemTypeID: itemTypeLaptop, StatusID: statusInUse, Notes: "hello"}
if err := as.Create(ctx, orig, nil); err != nil {
t.Fatalf("create: %v", err)
}
dup, err := as.Duplicate(ctx, orig.ID)
if err != nil {
t.Fatalf("duplicate: %v", err)
}
if dup.AssetTag == orig.AssetTag {
t.Fatalf("expected different asset tag, got same: %s", dup.AssetTag)
}
if dup.Notes != "hello" {
t.Fatalf("expected notes copied, got %q", dup.Notes)
}
}
func TestAssetListFilterByFolder(t *testing.T) {
ctx := context.Background()
as, fs, _ := testStores(t)
f1 := &models.Folder{Name: "Office"}
must(t, fs.Create(ctx, f1))
f2 := &models.Folder{Name: "Warehouse"}
must(t, fs.Create(ctx, f2))
a1 := &models.Asset{Name: "A1", ItemTypeID: itemTypeLaptop, StatusID: statusInUse, FolderID: &f1.ID}
must(t, as.Create(ctx, a1, nil))
a2 := &models.Asset{Name: "A2", ItemTypeID: itemTypeLaptop, StatusID: statusInUse, FolderID: &f2.ID}
must(t, as.Create(ctx, a2, nil))
list, total, err := as.List(ctx, assets.ListFilter{FolderID: &f1.ID, FolderSet: true, Page: 1, PageSize: 10})
if err != nil {
t.Fatalf("list: %v", err)
}
if total != 1 || len(list) != 1 || list[0].Name != "A1" {
t.Fatalf("unexpected list result: total=%d list=%+v", total, list)
}
}
func TestAssetCSVExportImportRoundTrip(t *testing.T) {
ctx := context.Background()
as, fs, db := testStores(t)
office := &models.Folder{Name: "Main Office"}
must(t, fs.Create(ctx, office))
orig := &models.Asset{Name: "Core Switch", ItemTypeID: itemTypeSwitch, StatusID: statusInUse, FolderID: &office.ID}
must(t, as.Create(ctx, orig, nil))
var buf bytes.Buffer
if err := as.Export(ctx, &buf, assets.ListFilter{}); err != nil {
t.Fatalf("export: %v", err)
}
if !strings.Contains(buf.String(), "Core Switch") {
t.Fatalf("expected exported CSV to contain asset name, got: %s", buf.String())
}
// Import a new asset referencing a not-yet-existing nested folder path,
// with CreateMissingFolders enabled.
csvData := "Name,Item Type,Folder,Status\nBackup Router,Switch,Main Office/Network,In Use\n"
itemTypes := seedItemTypes(t, db)
statuses := seedStatuses(t, db)
result, err := as.Import(ctx, strings.NewReader(csvData), assets.ImportOptions{CreateMissingFolders: true}, itemTypes, statuses, nil)
if err != nil {
t.Fatalf("import: %v", err)
}
if result.Created != 1 {
t.Fatalf("expected 1 created, got %+v", result)
}
all, total, err := as.List(ctx, assets.ListFilter{Page: 1, PageSize: 10})
if err != nil {
t.Fatalf("list: %v", err)
}
if total != 2 {
t.Fatalf("expected 2 assets total after import, got %d (%+v)", total, all)
}
}
func seedItemTypes(t *testing.T, db *sql.DB) []models.ItemType {
t.Helper()
rows, err := db.Query(`SELECT id, name, icon, enabled, sort_order FROM item_types`)
if err != nil {
t.Fatalf("query item types: %v", err)
}
defer rows.Close()
var out []models.ItemType
for rows.Next() {
var it models.ItemType
if err := rows.Scan(&it.ID, &it.Name, &it.Icon, &it.Enabled, &it.SortOrder); err != nil {
t.Fatalf("scan: %v", err)
}
out = append(out, it)
}
return out
}
func seedStatuses(t *testing.T, db *sql.DB) []models.Status {
t.Helper()
rows, err := db.Query(`SELECT id, name, color, sort_order FROM statuses`)
if err != nil {
t.Fatalf("query statuses: %v", err)
}
defer rows.Close()
var out []models.Status
for rows.Next() {
var st models.Status
if err := rows.Scan(&st.ID, &st.Name, &st.Color, &st.SortOrder); err != nil {
t.Fatalf("scan: %v", err)
}
out = append(out, st)
}
return out
}
func must(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}