// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. package dbf import ( "five/hbrt" "five/hbrdd" "os" "path/filepath" "testing" ) func tempDir(t *testing.T) string { t.Helper() dir := t.TempDir() return dir } func TestCreateAndOpenDBF(t *testing.T) { dir := tempDir(t) path := filepath.Join(dir, "test.dbf") // Create drv := &DBFDriver{} area, err := drv.Create(hbrdd.CreateParams{ Path: path, Fields: []hbrdd.FieldInfo{ {Name: "NAME", Type: 'C', Len: 20}, {Name: "AGE", Type: 'N', Len: 5, Dec: 0}, {Name: "ACTIVE", Type: 'L', Len: 1}, }, }) if err != nil { t.Fatal(err) } area.Close() // Verify file exists if _, err := os.Stat(path); err != nil { t.Fatal("DBF file not created") } // Open area2, err := drv.Open(hbrdd.OpenParams{Path: path}) if err != nil { t.Fatal(err) } defer area2.Close() if area2.FieldCount() != 3 { t.Errorf("field count = %d, want 3", area2.FieldCount()) } if area2.EOF() != true { t.Error("empty table should be EOF") } } func TestAppendAndRead(t *testing.T) { dir := tempDir(t) path := filepath.Join(dir, "test") drv := &DBFDriver{} area, err := drv.Create(hbrdd.CreateParams{ Path: path, Fields: []hbrdd.FieldInfo{ {Name: "NAME", Type: 'C', Len: 20}, {Name: "AGE", Type: 'N', Len: 5, Dec: 0}, {Name: "SALARY", Type: 'N', Len: 10, Dec: 2}, }, }) if err != nil { t.Fatal(err) } // Append 3 records for _, rec := range []struct { name string age int salary float64 }{ {"Kim", 30, 50000.00}, {"Lee", 25, 45000.50}, {"Park", 35, 60000.75}, } { area.Append() area.PutValue(0, hbrt.MakeString(rec.name)) area.PutValue(1, hbrt.MakeInt(rec.age)) area.PutValue(2, hbrt.MakeDouble(rec.salary, 10, 2)) area.Flush() } area.Close() // Reopen and verify area2, err := drv.Open(hbrdd.OpenParams{Path: path}) if err != nil { t.Fatal(err) } defer area2.Close() rc, _ := area2.RecCount() if rc != 3 { t.Fatalf("reccount = %d, want 3", rc) } // Read record 1 area2.GoTo(1) name, _ := area2.GetValue(0) age, _ := area2.GetValue(1) salary, _ := area2.GetValue(2) if got := name.AsString(); got[:3] != "Kim" { t.Errorf("rec 1 name = %q, want Kim...", got) } if age.AsNumInt() != 30 { t.Errorf("rec 1 age = %d, want 30", age.AsNumInt()) } if salary.AsDouble() != 50000.00 { t.Errorf("rec 1 salary = %f, want 50000.00", salary.AsDouble()) } // Read record 3 area2.GoTo(3) name, _ = area2.GetValue(0) if got := name.AsString(); got[:4] != "Park" { t.Errorf("rec 3 name = %q, want Park...", got) } } func TestGoTopSkipEOF(t *testing.T) { dir := tempDir(t) path := filepath.Join(dir, "test") drv := &DBFDriver{} area, _ := drv.Create(hbrdd.CreateParams{ Path: path, Fields: []hbrdd.FieldInfo{ {Name: "ID", Type: 'N', Len: 5}, }, }) for i := 1; i <= 5; i++ { area.Append() area.PutValue(0, hbrt.MakeInt(i)) area.Flush() } area.Close() area2, _ := drv.Open(hbrdd.OpenParams{Path: path}) defer area2.Close() // GO TOP area2.GoTop() if area2.RecNo() != 1 { t.Errorf("GoTop recno = %d, want 1", area2.RecNo()) } if area2.BOF() { t.Error("GoTop should not be BOF") } // SKIP forward for i := 0; i < 4; i++ { area2.Skip(1) } if area2.RecNo() != 5 { t.Errorf("after 4 skips recno = %d, want 5", area2.RecNo()) } // SKIP past last area2.Skip(1) if !area2.EOF() { t.Error("should be EOF after skipping past last record") } // GO BOTTOM area2.GoBottom() if area2.RecNo() != 5 { t.Errorf("GoBottom recno = %d, want 5", area2.RecNo()) } // SKIP backward to BOF for i := 0; i < 5; i++ { area2.Skip(-1) } if !area2.BOF() { t.Error("should be BOF after skipping backward past first record") } } func TestDeleteRecallPack(t *testing.T) { dir := tempDir(t) path := filepath.Join(dir, "test") drv := &DBFDriver{} area, _ := drv.Create(hbrdd.CreateParams{ Path: path, Fields: []hbrdd.FieldInfo{ {Name: "ID", Type: 'N', Len: 5}, }, }) for i := 1; i <= 5; i++ { area.Append() area.PutValue(0, hbrt.MakeInt(i)) area.Flush() } // Delete record 2 and 4 area.GoTo(2) area.Delete() area.Flush() area.GoTo(4) area.Delete() area.Flush() // Verify deleted area.GoTo(2) if !area.Deleted() { t.Error("record 2 should be deleted") } // Recall record 2 area.GoTo(2) area.Recall() area.Flush() area.GoTo(2) if area.Deleted() { t.Error("record 2 should be recalled") } // Pack (removes record 4 which is still deleted) area.Pack() rc, _ := area.RecCount() if rc != 4 { t.Errorf("after pack reccount = %d, want 4", rc) } // Verify remaining records: 1, 2, 3, 5 area.GoTo(4) v, _ := area.GetValue(0) if v.AsNumInt() != 5 { t.Errorf("after pack rec 4 ID = %d, want 5", v.AsNumInt()) } area.Close() } func TestZap(t *testing.T) { dir := tempDir(t) path := filepath.Join(dir, "test") drv := &DBFDriver{} area, _ := drv.Create(hbrdd.CreateParams{ Path: path, Fields: []hbrdd.FieldInfo{{Name: "ID", Type: 'N', Len: 5}}, }) for i := 1; i <= 10; i++ { area.Append() area.PutValue(0, hbrt.MakeInt(i)) area.Flush() } rc, _ := area.RecCount() if rc != 10 { t.Fatalf("before zap: %d", rc) } area.Zap() rc, _ = area.RecCount() if rc != 0 { t.Errorf("after zap: %d, want 0", rc) } if !area.EOF() { t.Error("after zap should be EOF") } area.Close() } func TestLogicalField(t *testing.T) { dir := tempDir(t) path := filepath.Join(dir, "test") drv := &DBFDriver{} area, _ := drv.Create(hbrdd.CreateParams{ Path: path, Fields: []hbrdd.FieldInfo{ {Name: "FLAG", Type: 'L', Len: 1}, }, }) area.Append() area.PutValue(0, hbrt.MakeBool(true)) area.Flush() area.Append() area.PutValue(0, hbrt.MakeBool(false)) area.Flush() area.Close() area2, _ := drv.Open(hbrdd.OpenParams{Path: path}) defer area2.Close() area2.GoTo(1) v, _ := area2.GetValue(0) if !v.AsBool() { t.Error("rec 1 should be TRUE") } area2.GoTo(2) v, _ = area2.GetValue(0) if v.AsBool() { t.Error("rec 2 should be FALSE") } } func TestFieldTypes(t *testing.T) { dir := tempDir(t) path := filepath.Join(dir, "test") drv := &DBFDriver{} area, _ := drv.Create(hbrdd.CreateParams{ Path: path, Fields: []hbrdd.FieldInfo{ {Name: "CHAR", Type: 'C', Len: 10}, {Name: "NUM", Type: 'N', Len: 10, Dec: 2}, {Name: "LOGIC", Type: 'L', Len: 1}, {Name: "DATE", Type: 'D', Len: 8}, }, }) area.Append() area.PutValue(0, hbrt.MakeString("Hello")) area.PutValue(1, hbrt.MakeDouble(123.45, 10, 2)) area.PutValue(2, hbrt.MakeBool(true)) area.PutValue(3, hbrt.MakeDate(2461033)) // 2026-03-27 area.Flush() area.Close() area2, _ := drv.Open(hbrdd.OpenParams{Path: path}) defer area2.Close() area2.GoTo(1) v0, _ := area2.GetValue(0) if v0.AsString()[:5] != "Hello" { t.Errorf("CHAR = %q", v0.AsString()) } v1, _ := area2.GetValue(1) if v1.AsDouble() != 123.45 { t.Errorf("NUM = %f, want 123.45", v1.AsDouble()) } v2, _ := area2.GetValue(2) if !v2.AsBool() { t.Error("LOGIC should be TRUE") } v3, _ := area2.GetValue(3) if v3.AsJulian() != 2461033 { t.Errorf("DATE julian = %d, want 2461033", v3.AsJulian()) } }