Compare commits

6 Commits

9 changed files with 944 additions and 4 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/bin
/reports

View File

@ -10,6 +10,7 @@ COMMIT_DATETIME := $(shell git log -1 --format=%cd --date=iso8601)
LDFLAGS := -X git.omicron.one/omicron/linkshare/internal/version.Version=$(VERSION) \
-X git.omicron.one/omicron/linkshare/internal/version.GitCommit=$(COMMIT) \
-X "git.omicron.one/omicron/linkshare/internal/version.CommitDateTime=$(COMMIT_DATETIME)"
OPEN = xdg-open
all: build
@ -25,7 +26,10 @@ $(BINARIES): %: $(BINARY_DIR)
go build -ldflags '$(LDFLAGS)' -o $(BINARY_DIR)/$@ ./cmd/$@/
test:
go test ./...
mkdir -p reports/coverage/
go test ./... -coverprofile=reports/coverage/coverage.out
go tool cover -html=reports/coverage/coverage.out -o reports/coverage/coverage.html && $(OPEN) reports/coverage/coverage.html
validate:
@test -z "$(shell gofumpt -l .)" && echo "No files need formatting" || (echo "Incorrect formatting in:"; gofumpt -l .; exit 1)
@ -33,6 +37,7 @@ validate:
clean:
rm -rf $(BINARY_DIR)
rm -rf reports
go clean
run: $(LINKSERV)

View File

@ -0,0 +1,192 @@
package links
import (
"database/sql"
"time"
"git.omicron.one/omicron/linkshare/internal/database"
. "git.omicron.one/omicron/linkshare/internal/util/option"
)
// Link represents a stored link
type Link struct {
ID int64
URL string
Title string
CreatedAt time.Time
UpdatedAt Option[time.Time]
IsPrivate bool
}
// Repository handles link storage operations
type Repository struct {
db *database.DB
}
// NewRepository creates a new link repository
func NewRepository(db *database.DB) *Repository {
return &Repository{db: db}
}
// Create adds a new link to the database
func (r *Repository) Create(url, title string, isPrivate bool) (int64, error) {
var id int64
err := r.db.Transaction(func(tx *sql.Tx) error {
now := time.Now().UTC().Format(time.RFC3339)
result, err := tx.Exec(
"INSERT INTO links (url, title, created_at, is_private) VALUES (?, ?, ?, ?)",
url, title, now, isPrivate,
)
if err != nil {
return err
}
id, err = result.LastInsertId()
return err
})
return id, err
}
// Get retrieves a single link by ID
func (r *Repository) Get(id int64) (*Link, error) {
var (
link Link
createdAt string
updatedAt sql.NullString
)
err := r.db.Transaction(func(tx *sql.Tx) error {
row := tx.QueryRow(
"SELECT id, url, title, created_at, updated_at, is_private FROM links WHERE id = ?",
id,
)
err := row.Scan(&link.ID, &link.URL, &link.Title, &createdAt, &updatedAt, &link.IsPrivate)
if err != nil {
return err
}
created, err := time.Parse(time.RFC3339, createdAt)
if err != nil {
return err
}
link.CreatedAt = created
if updatedAt.Valid {
updated, err := time.Parse(time.RFC3339, updatedAt.String)
if err != nil {
return err
}
link.UpdatedAt = Some(updated)
} else {
link.UpdatedAt = None[time.Time]()
}
return nil
})
if err != nil {
return nil, err
}
return &link, nil
}
// Update updates an existing link's fields
func (r *Repository) Update(id int64, url, title string, isPrivate bool) error {
return r.db.Transaction(func(tx *sql.Tx) error {
now := time.Now().UTC().Format(time.RFC3339)
_, err := tx.Exec(
"UPDATE links SET url = ?, title = ?, updated_at = ?, is_private = ? WHERE id = ?",
url, title, now, isPrivate, id,
)
return err
})
}
// Delete removes a link from the database
func (r *Repository) Delete(id int64) error {
return r.db.Transaction(func(tx *sql.Tx) error {
_, err := tx.Exec("DELETE FROM links WHERE id = ?", id)
return err
})
}
// List returns a paginated list of links
func (r *Repository) List(includePrivate bool, offset, limit int) ([]*Link, error) {
var links []*Link
err := r.db.Transaction(func(tx *sql.Tx) error {
var rows *sql.Rows
var err error
if includePrivate {
rows, err = tx.Query(
`SELECT id, url, title, created_at, updated_at, is_private
FROM links ORDER BY created_at DESC LIMIT ? OFFSET ?`,
limit, offset,
)
} else {
rows, err = tx.Query(
`SELECT id, url, title, created_at, updated_at, is_private
FROM links WHERE is_private = 0 ORDER BY created_at DESC LIMIT ? OFFSET ?`,
limit, offset,
)
}
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var (
link Link
createdAt string
updatedAt sql.NullString
)
err := rows.Scan(&link.ID, &link.URL, &link.Title, &createdAt, &updatedAt, &link.IsPrivate)
if err != nil {
return err
}
created, err := time.Parse(time.RFC3339, createdAt)
if err != nil {
return err
}
link.CreatedAt = created
if updatedAt.Valid {
updated, err := time.Parse(time.RFC3339, updatedAt.String)
if err != nil {
return err
}
link.UpdatedAt = Some(updated)
} else {
link.UpdatedAt = None[time.Time]()
}
links = append(links, &link)
}
return rows.Err()
})
if err != nil {
return nil, err
}
return links, nil
}
// Count returns the total number of links in the database
func (r *Repository) Count(includePrivate bool) (int, error) {
var count int
err := r.db.Transaction(func(tx *sql.Tx) error {
var row *sql.Row
if includePrivate {
row = tx.QueryRow("SELECT COUNT(*) FROM links")
} else {
row = tx.QueryRow("SELECT COUNT(*) FROM links WHERE is_private")
}
return row.Scan(&count)
})
return count, err
}

View File

@ -0,0 +1,329 @@
package links_test
import (
"os"
"testing"
"time"
"git.omicron.one/omicron/linkshare/internal/database"
"git.omicron.one/omicron/linkshare/internal/database/links"
)
func setupTestDB(t *testing.T) (*database.DB, string) {
t.Helper()
cwd, err := os.Getwd()
t.Logf("Current working directory: %s", cwd)
// Create temp file for database
tempFile, err := os.CreateTemp("", "linkshare-links-test-*.db")
if err != nil {
t.Fatalf("Failed to create temp file: %v", err)
}
tempFile.Close()
dbPath := tempFile.Name()
// Open database
db, err := database.Open(dbPath)
if err != nil {
os.Remove(dbPath)
t.Fatalf("Failed to open database: %v", err)
}
// Initialize database with schema
err = db.Initialize("../../../schema")
if err != nil {
db.Close()
os.Remove(dbPath)
t.Fatalf("Failed to initialize database: %v", err)
}
return db, dbPath
}
func TestRepository_Create(t *testing.T) {
db, dbPath := setupTestDB(t)
defer func() {
db.Close()
os.Remove(dbPath)
}()
repo := links.NewRepository(db)
// Test creating a link
id, err := repo.Create("https://example.com", "Example", false)
if err != nil {
t.Fatalf("Failed to create link: %v", err)
}
if id <= 0 {
t.Fatalf("Expected positive ID, got %d", id)
}
// Verify link was created by retrieving it
link, err := repo.Get(id)
if err != nil {
t.Fatalf("Failed to get link: %v", err)
}
if link.URL != "https://example.com" {
t.Errorf("Expected URL 'https://example.com', got '%s'", link.URL)
}
}
func TestRepository_Get(t *testing.T) {
db, dbPath := setupTestDB(t)
defer func() {
db.Close()
os.Remove(dbPath)
}()
repo := links.NewRepository(db)
// Insert test data
id, err := repo.Create("https://example.com", "Example", true)
if err != nil {
t.Fatalf("Failed to create link: %v", err)
}
// Test getting a link
link, err := repo.Get(id)
if err != nil {
t.Fatalf("Failed to get link: %v", err)
}
if link.ID != id {
t.Errorf("Expected ID %d, got %d", id, link.ID)
}
if link.URL != "https://example.com" {
t.Errorf("Expected URL 'https://example.com', got '%s'", link.URL)
}
if link.Title != "Example" {
t.Errorf("Expected Title 'Example', got '%s'", link.Title)
}
if link.IsPrivate != true {
t.Errorf("Expected IsPrivate true, got %v", link.IsPrivate)
}
if link.UpdatedAt.IsSome() {
t.Errorf("Expected UpdatedAt to be None, got %v", link.UpdatedAt)
}
// Test getting non-existent link
_, err = repo.Get(id + 1)
if err == nil {
t.Fatal("Expected error when getting non-existent link")
}
}
func TestRepository_Update(t *testing.T) {
db, dbPath := setupTestDB(t)
defer func() {
db.Close()
os.Remove(dbPath)
}()
repo := links.NewRepository(db)
// Insert test data
id, err := repo.Create("https://example.com", "Example", false)
if err != nil {
t.Fatalf("Failed to create link: %v", err)
}
// Test updating a link
err = repo.Update(id, "https://updated.com", "Updated", true)
if err != nil {
t.Fatalf("Failed to update link: %v", err)
}
// Verify link was updated
link, err := repo.Get(id)
if err != nil {
t.Fatalf("Failed to get link: %v", err)
}
if link.URL != "https://updated.com" {
t.Errorf("Expected URL 'https://updated.com', got '%s'", link.URL)
}
if link.Title != "Updated" {
t.Errorf("Expected Title 'Updated', got '%s'", link.Title)
}
if link.IsPrivate != true {
t.Errorf("Expected IsPrivate true, got %v", link.IsPrivate)
}
if !link.UpdatedAt.IsSome() {
t.Error("Expected UpdatedAt to be set")
}
}
func TestRepository_Delete(t *testing.T) {
db, dbPath := setupTestDB(t)
defer func() {
db.Close()
os.Remove(dbPath)
}()
repo := links.NewRepository(db)
// Insert test data
id, err := repo.Create("https://example.com", "Example", false)
if err != nil {
t.Fatalf("Failed to create link: %v", err)
}
// Test deleting a link
err = repo.Delete(id)
if err != nil {
t.Fatalf("Failed to delete link: %v", err)
}
// Verify link was deleted
_, err = repo.Get(id)
if err == nil {
t.Fatal("Expected error after deletion")
}
}
func TestRepository_List(t *testing.T) {
db, dbPath := setupTestDB(t)
defer func() {
db.Close()
os.Remove(dbPath)
}()
repo := links.NewRepository(db)
// Insert test data
urls := []struct {
url string
isPrivate bool
}{
{"https://example1.com", true},
{"https://example2.com", false},
{"https://example3.com", false},
{"https://example4.com", true},
{"https://example5.com", false},
}
for i, info := range urls {
_, err := repo.Create(info.url, "Example "+string(rune('A'+i)), info.isPrivate)
if err != nil {
t.Fatalf("Failed to create link: %v", err)
}
// Add a small delay to ensure different created_at times
time.Sleep(10 * time.Millisecond)
}
// Test full listing with pagination
links, err := repo.List(true, 0, 3)
if err != nil {
t.Fatalf("Failed to list links: %v", err)
}
if len(links) != 3 {
t.Fatalf("Expected 3 links, got %d", len(links))
}
// Check order (newest first)
for i := 0; i < len(links)-1; i++ {
if links[i].CreatedAt.Before(links[i+1].CreatedAt) {
t.Errorf("Links not in correct order")
}
}
// Test second page of full listing
links, err = repo.List(true, 3, 2)
if err != nil {
t.Fatalf("Failed to list links: %v", err)
}
if len(links) != 2 {
t.Fatalf("Expected 2 links, got %d", len(links))
}
// Test public listing
links, err = repo.List(false, 0, 3)
if err != nil {
t.Fatalf("Failed to list links: %v", err)
}
if len(links) != 3 {
t.Fatalf("Expected 3 links, got %d", len(links))
}
for _, link := range links {
if link.IsPrivate {
t.Fatalf("private link in public listing %v", link)
}
}
// Try to get more public links
links, err = repo.List(false, 3, 3)
if err != nil {
t.Fatalf("Failed to list links: %v", err)
}
if len(links) != 0 {
t.Fatalf("Expected 0 links, got %d", len(links))
}
}
func TestRepository_Count(t *testing.T) {
db, dbPath := setupTestDB(t)
defer func() {
db.Close()
os.Remove(dbPath)
}()
repo := links.NewRepository(db)
// Check full count with empty table
count, err := repo.Count(true)
if err != nil {
t.Fatalf("Failed to count links: %v", err)
}
if count != 0 {
t.Fatalf("Expected 0 links, got %d", count)
}
// Check public count with empty table
count, err = repo.Count(false)
if err != nil {
t.Fatalf("Failed to count links: %v", err)
}
if count != 0 {
t.Fatalf("Expected 0 links, got %d", count)
}
// Insert test data
numLinks := 5
for i := 0; i < numLinks; i++ {
_, err := repo.Create(
"https://example"+string(rune('1'+i))+".com",
"Example "+string(rune('A'+i)),
i%2 == 1,
)
if err != nil {
t.Fatalf("Failed to create link: %v", err)
}
}
pubLinks := numLinks / 2
// Check full count again
count, err = repo.Count(true)
if err != nil {
t.Fatalf("Failed to count links: %v", err)
}
if count != numLinks {
t.Fatalf("Expected %d links, got %d", numLinks, count)
}
// Check public count again
count, err = repo.Count(false)
if err != nil {
t.Fatalf("Failed to count links: %v", err)
}
if count != pubLinks {
t.Fatalf("Expected %d links, got %d", pubLinks, count)
}
}

View File

@ -5,7 +5,6 @@
package database
import (
"context"
"database/sql"
"errors"
"fmt"
@ -135,8 +134,12 @@ func (db *DB) CheckSchemaVersion() error {
return nil
}
func (db *DB) transaction(ctx context.Context, fn func(*sql.Tx) error) error {
tx, err := db.conn.BeginTx(ctx, nil)
// Transaction executes the provided function within a SQL transaction.
// If the function returns an error, the transaction is rolled back.
// If the function panics, the transaction is rolled back and the panic is re-thrown.
// The function receives a *sql.Tx that can be used for database operations.
func (db *DB) Transaction(fn func(*sql.Tx) error) error {
tx, err := db.conn.Begin()
if err != nil {
return fmt.Errorf("failed to begin transaction: %w", err)
}

View File

@ -0,0 +1,314 @@
package database
import (
"database/sql"
"os"
"path/filepath"
"testing"
"git.omicron.one/omicron/linkshare/internal/version"
)
func TestOpenClose(t *testing.T) {
// Create temp file for database
tempFile, err := os.CreateTemp("", "linkshare-test-*.db")
if err != nil {
t.Fatalf("Failed to create temp file: %v", err)
}
defer os.Remove(tempFile.Name())
tempFile.Close()
// Test opening
db, err := Open(tempFile.Name())
if err != nil {
t.Fatalf("Failed to open database: %v", err)
}
// Test closing
err = db.Close()
if err != nil {
t.Fatalf("Failed to close database: %v", err)
}
}
func TestInitialize(t *testing.T) {
// Create temp directory for test data
tempDir, err := os.MkdirTemp("", "linkshare-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tempDir)
// Create schema directory and current.sql file
schemaDir := filepath.Join(tempDir, "schema")
err = os.Mkdir(schemaDir, 0o755)
if err != nil {
t.Fatalf("Failed to create schema directory: %v", err)
}
// Write test schema to file
schemaContent := `CREATE TABLE settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
kind TEXT CHECK(kind IN ('int', 'string', 'bool', 'json', 'glob')) NOT NULL
);
INSERT INTO settings (key, value, kind) VALUES ('schema-version', '1', 'int');`
err = os.WriteFile(filepath.Join(schemaDir, "current.sql"), []byte(schemaContent), 0o644)
if err != nil {
t.Fatalf("Failed to write schema file: %v", err)
}
// Create temp database file
dbPath := filepath.Join(tempDir, "test.db")
// Open database
db, err := Open(dbPath)
if err != nil {
t.Fatalf("Failed to open database: %v", err)
}
defer db.Close()
// Test initialization
err = db.Initialize(schemaDir)
if err != nil {
t.Fatalf("Failed to initialize database: %v", err)
}
// Test already initialized error
err = db.Initialize(schemaDir)
if err != ErrAlreadyInitialized {
t.Fatalf("Expected ErrAlreadyInitialized, got: %v", err)
}
}
func TestCheckInitialized(t *testing.T) {
// Create temp file for database
tempFile, err := os.CreateTemp("", "linkshare-test-*.db")
if err != nil {
t.Fatalf("Failed to create temp file: %v", err)
}
defer os.Remove(tempFile.Name())
tempFile.Close()
// Open database
db, err := Open(tempFile.Name())
if err != nil {
t.Fatalf("Failed to open database: %v", err)
}
defer db.Close()
// Test not initialized
err = db.CheckInitialized()
if err != ErrNotInitialized {
t.Fatalf("Expected ErrNotInitialized, got: %v", err)
}
// Initialize the database manually for testing
_, err = db.conn.Exec("CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT NOT NULL, kind TEXT NOT NULL)")
if err != nil {
t.Fatalf("Failed to create settings table: %v", err)
}
// Test initialized
err = db.CheckInitialized()
if err != nil {
t.Fatalf("Expected nil error after initialization, got: %v", err)
}
}
func TestGetSchemaVersion(t *testing.T) {
// Create temp file for database
tempFile, err := os.CreateTemp("", "linkshare-test-*.db")
if err != nil {
t.Fatalf("Failed to create temp file: %v", err)
}
defer os.Remove(tempFile.Name())
tempFile.Close()
// Open database
db, err := Open(tempFile.Name())
if err != nil {
t.Fatalf("Failed to open database: %v", err)
}
defer db.Close()
// Initialize the database manually for testing
_, err = db.conn.Exec("CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT NOT NULL, kind TEXT NOT NULL)")
if err != nil {
t.Fatalf("Failed to create settings table: %v", err)
}
_, err = db.conn.Exec("INSERT INTO settings (key, value, kind) VALUES ('schema-version', '1', 'int')")
if err != nil {
t.Fatalf("Failed to insert schema version: %v", err)
}
// Test schema version
version, err := db.GetSchemaVersion()
if err != nil {
t.Fatalf("Failed to get schema version: %v", err)
}
if version != 1 {
t.Fatalf("Expected schema version 1, got: %d", version)
}
// Test invalid schema version
_, err = db.conn.Exec("UPDATE settings SET value = 'invalid' WHERE key = 'schema-version'")
if err != nil {
t.Fatalf("Failed to update schema version: %v", err)
}
_, err = db.GetSchemaVersion()
if err == nil {
t.Fatal("Expected error for invalid schema version, got nil")
}
}
func TestCheckSchemaVersion(t *testing.T) {
// Create temp file for database
tempFile, err := os.CreateTemp("", "linkshare-test-*.db")
if err != nil {
t.Fatalf("Failed to create temp file: %v", err)
}
defer os.Remove(tempFile.Name())
tempFile.Close()
// Open database
db, err := Open(tempFile.Name())
if err != nil {
t.Fatalf("Failed to open database: %v", err)
}
defer db.Close()
// Test not initialized
err = db.CheckSchemaVersion()
if err != ErrNotInitialized {
t.Fatalf("Expected ErrNotInitialized, got: %v", err)
}
// Initialize the database manually
_, err = db.conn.Exec("CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT NOT NULL, kind TEXT NOT NULL)")
if err != nil {
t.Fatalf("Failed to create settings table: %v", err)
}
// Store current schema version
originalSchemaVersion := version.SchemaVersion
defer func() {
// Restore original schema version after test
version.SchemaVersion = originalSchemaVersion
}()
// Test version match
_, err = db.conn.Exec("INSERT INTO settings (key, value, kind) VALUES ('schema-version', '1', 'int')")
if err != nil {
t.Fatalf("Failed to insert schema version: %v", err)
}
version.SchemaVersion = 1
err = db.CheckSchemaVersion()
if err != nil {
t.Fatalf("Expected nil error for matching schema versions, got: %v", err)
}
// Test outdated version
version.SchemaVersion = 2
err = db.CheckSchemaVersion()
if err != ErrSchemaOutdated {
t.Fatalf("Expected ErrSchemaOutdated, got: %v", err)
}
// Test unsupported version
version.SchemaVersion = 1
_, err = db.conn.Exec("UPDATE settings SET value = '2' WHERE key = 'schema-version'")
if err != nil {
t.Fatalf("Failed to update schema version: %v", err)
}
err = db.CheckSchemaVersion()
if err != ErrSchemaUnsupported {
t.Fatalf("Expected ErrSchemaUnsupported, got: %v", err)
}
}
func TestTransaction(t *testing.T) {
// Create temp file for database
tempFile, err := os.CreateTemp("", "linkshare-test-*.db")
if err != nil {
t.Fatalf("Failed to create temp file: %v", err)
}
defer os.Remove(tempFile.Name())
tempFile.Close()
// Open database
db, err := Open(tempFile.Name())
if err != nil {
t.Fatalf("Failed to open database: %v", err)
}
defer db.Close()
// Initialize the database manually for testing
_, err = db.conn.Exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")
if err != nil {
t.Fatalf("Failed to create test table: %v", err)
}
// Test successful transaction
err = db.Transaction(func(tx *sql.Tx) error {
_, err := tx.Exec("INSERT INTO test (value) VALUES (?)", "test-value")
return err
})
if err != nil {
t.Fatalf("Transaction failed: %v", err)
}
// Verify data was inserted
var value string
err = db.conn.QueryRow("SELECT value FROM test WHERE id = 1").Scan(&value)
if err != nil {
t.Fatalf("Failed to query test value: %v", err)
}
if value != "test-value" {
t.Fatalf("Expected 'test-value', got: %s", value)
}
// Test failed transaction
err = db.Transaction(func(tx *sql.Tx) error {
_, err := tx.Exec("INSERT INTO test (value) VALUES (?)", "should-rollback")
if err != nil {
return err
}
return sql.ErrTxDone // Force rollback
})
if err == nil {
t.Fatal("Expected error from failed transaction, got nil")
}
// Verify data was not inserted (rollback worked)
var count int
err = db.conn.QueryRow("SELECT COUNT(*) FROM test WHERE value = 'should-rollback'").Scan(&count)
if err != nil {
t.Fatalf("Failed to query test count: %v", err)
}
if count != 0 {
t.Fatalf("Expected count 0 after rollback, got: %d", count)
}
// Test panic in transaction
panicked := false
func() {
defer func() {
if r := recover(); r != nil {
panicked = true
}
}()
_ = db.Transaction(func(tx *sql.Tx) error {
panic("test panic")
})
}()
if !panicked {
t.Fatal("Expected panic to be propagated")
}
}

View File

@ -0,0 +1,41 @@
package option
type Option[T any] struct {
hasValue bool
value T
}
func Some[T any](value T) Option[T] {
return Option[T]{
hasValue: true,
value: value,
}
}
func None[T any]() Option[T] {
return Option[T]{
hasValue: false,
}
}
func (o Option[T]) IsSome() bool {
return o.hasValue
}
func (o Option[T]) IsNone() bool {
return !o.hasValue
}
func (o Option[T]) Value() T {
if !o.hasValue {
panic("Option has no value")
}
return o.value
}
func (o Option[T]) ValueOr(defaultValue T) T {
if !o.hasValue {
return defaultValue
}
return o.value
}

View File

@ -0,0 +1,54 @@
package option_test
import (
"testing"
. "git.omicron.one/omicron/linkshare/internal/util/option"
)
func TestSome(t *testing.T) {
opt := Some(42)
if !opt.IsSome() {
t.Error("Expected IsSome() to be true for Some(42)")
}
if opt.IsNone() {
t.Error("Expected IsNone() to be false for Some(42)")
}
if opt.Value() != 42 {
t.Errorf("Expected Value() to be 42, got %v", opt.Value())
}
if opt.ValueOr(0) != 42 {
t.Errorf("Expected ValueOr(0) to be 42, got %v", opt.ValueOr(0))
}
}
func TestNone(t *testing.T) {
opt := None[int]()
if opt.IsSome() {
t.Error("Expected IsSome() to be false for None[int]()")
}
if !opt.IsNone() {
t.Error("Expected IsNone() to be true for None[int]()")
}
if opt.ValueOr(99) != 99 {
t.Errorf("Expected ValueOr(99) to be 99, got %v", opt.ValueOr(99))
}
}
func TestPanic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Error("Expected Value() to panic on None")
}
}()
opt := None[string]()
_ = opt.Value() // This should panic
}

View File

@ -16,4 +16,5 @@ CREATE TABLE links (
);
CREATE INDEX idx_links_created_at ON links(created_at);
CREATE INDEX idx_links_is_private_created_at ON links(is_private, created_at);
CREATE INDEX idx_links_url ON links(url);