Files
dreamdict/internal/loader/wiktionary_test.go
prosolis eeea8d4d34 Initial commit: DreamDict HTTP dictionary service
Go-based dictionary backend providing word validation, definitions, synonyms,
and cross-language translation for English, French, and Portuguese. Designed
as a self-hosted replacement for Wordnik, backing GogoBee.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 16:57:40 -07:00

188 lines
5.2 KiB
Go

package loader
import (
"database/sql"
"os"
"path/filepath"
"testing"
"dreamdict/internal/dictionary"
_ "modernc.org/sqlite"
)
func setupWiktTestDB(t *testing.T) *sql.DB {
t.Helper()
db, err := sql.Open("sqlite", ":memory:?_pragma=foreign_keys(on)")
if err != nil {
t.Fatal(err)
}
db.SetMaxOpenConns(1)
if err := dictionary.BootstrapSchema(db); err != nil {
t.Fatal(err)
}
return db
}
func writeJSONL(t *testing.T, dir, name, content string) string {
t.Helper()
path := filepath.Join(dir, name)
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
t.Fatal(err)
}
return path
}
func TestFormOfFiltering(t *testing.T) {
db := setupWiktTestDB(t)
defer db.Close()
// Seed a word so definitions can reference it
if _, err := db.Exec("INSERT INTO words (word, lang) VALUES ('run', 'en')"); err != nil {
t.Fatal(err)
}
dir := t.TempDir()
jsonl := `{"word":"run","pos":"verb","lang_code":"en","senses":[{"glosses":["to move quickly"],"tags":[]},{"glosses":["past tense of run"],"tags":["form-of"]},{"glosses":["alternative form"],"tags":["alt-of"]}],"synonyms":[],"translations":[]}` + "\n"
path := writeJSONL(t, dir, "test.jsonl", jsonl)
if err := loadWiktionary(db, path, "en"); err != nil {
t.Fatal(err)
}
// Only the first sense should be loaded — form-of and alt-of should be filtered
var count int
if err := db.QueryRow("SELECT COUNT(*) FROM definitions").Scan(&count); err != nil {
t.Fatal(err)
}
if count != 1 {
t.Errorf("expected 1 definition (form-of filtered), got %d", count)
}
// Verify the correct gloss was kept
var gloss string
if err := db.QueryRow("SELECT gloss FROM definitions").Scan(&gloss); err != nil {
t.Fatal(err)
}
if gloss != "to move quickly" {
t.Errorf("expected 'to move quickly', got %q", gloss)
}
}
func TestBrazilFiltering(t *testing.T) {
db := setupWiktTestDB(t)
defer db.Close()
if _, err := db.Exec("INSERT INTO words (word, lang) VALUES ('autocarro', 'pt-PT')"); err != nil {
t.Fatal(err)
}
dir := t.TempDir()
jsonl := `{"word":"autocarro","pos":"noun","lang_code":"pt","senses":[{"glosses":["a bus (Portugal)"],"tags":["Portugal"]},{"glosses":["a bus (Brazil)"],"tags":["Brazil"]},{"glosses":["a vehicle for transport"],"tags":[]}],"synonyms":[],"translations":[]}` + "\n"
path := writeJSONL(t, dir, "test.jsonl", jsonl)
if err := loadWiktionary(db, path, "pt-PT"); err != nil {
t.Fatal(err)
}
// Brazil-tagged sense should be filtered; Portugal-tagged and untagged should remain
var count int
if err := db.QueryRow("SELECT COUNT(*) FROM definitions").Scan(&count); err != nil {
t.Fatal(err)
}
if count != 2 {
t.Errorf("expected 2 definitions (Brazil filtered), got %d", count)
}
}
func TestBrazilianPortugueseTagFiltering(t *testing.T) {
db := setupWiktTestDB(t)
defer db.Close()
if _, err := db.Exec("INSERT INTO words (word, lang) VALUES ('ônibus', 'pt-PT')"); err != nil {
t.Fatal(err)
}
dir := t.TempDir()
jsonl := `{"word":"ônibus","pos":"noun","lang_code":"pt","senses":[{"glosses":["a bus"],"tags":["Brazilian Portuguese"]}],"synonyms":[],"translations":[]}` + "\n"
path := writeJSONL(t, dir, "test.jsonl", jsonl)
if err := loadWiktionary(db, path, "pt-PT"); err != nil {
t.Fatal(err)
}
var count int
if err := db.QueryRow("SELECT COUNT(*) FROM definitions").Scan(&count); err != nil {
t.Fatal(err)
}
if count != 0 {
t.Errorf("expected 0 definitions (Brazilian Portuguese filtered), got %d", count)
}
}
func TestGlossFiltering(t *testing.T) {
db := setupWiktTestDB(t)
defer db.Close()
if _, err := db.Exec("INSERT INTO words (word, lang) VALUES ('test', 'en')"); err != nil {
t.Fatal(err)
}
dir := t.TempDir()
jsonl := `{"word":"test","pos":"noun","lang_code":"en","senses":[{"glosses":["a valid definition"],"tags":[]},{"glosses":["(archaic form)"],"tags":[]},{"glosses":[""],"tags":[]}],"synonyms":[],"translations":[]}` + "\n"
path := writeJSONL(t, dir, "test.jsonl", jsonl)
if err := loadWiktionary(db, path, "en"); err != nil {
t.Fatal(err)
}
// Only "a valid definition" should pass — parenthetical and empty filtered
var count int
if err := db.QueryRow("SELECT COUNT(*) FROM definitions").Scan(&count); err != nil {
t.Fatal(err)
}
if count != 1 {
t.Errorf("expected 1 definition (gloss filtering), got %d", count)
}
}
func TestHasTag(t *testing.T) {
tests := []struct {
tags []string
targets []string
want bool
}{
{[]string{"form-of", "verb"}, []string{"form-of"}, true},
{[]string{"verb"}, []string{"form-of", "alt-of"}, false},
{[]string{"alt-of"}, []string{"form-of", "alt-of"}, true},
{nil, []string{"form-of"}, false},
{[]string{}, []string{"form-of"}, false},
}
for _, tt := range tests {
got := hasTag(tt.tags, tt.targets...)
if got != tt.want {
t.Errorf("hasTag(%v, %v) = %v, want %v", tt.tags, tt.targets, got, tt.want)
}
}
}
func TestHasBrazilTag(t *testing.T) {
tests := []struct {
tags []string
want bool
}{
{[]string{"Brazil"}, true},
{[]string{"Brazilian Portuguese"}, true},
{[]string{"Portugal"}, false},
{[]string{"European Portuguese"}, false},
{nil, false},
{[]string{}, false},
}
for _, tt := range tests {
got := hasBrazilTag(tt.tags)
if got != tt.want {
t.Errorf("hasBrazilTag(%v) = %v, want %v", tt.tags, got, tt.want)
}
}
}