This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
package sq
|
||||
|
||||
import (
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/tst"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildInsertStatementBasic(t *testing.T) {
|
||||
type r struct {
|
||||
ID string `db:"id"`
|
||||
Name string `db:"name"`
|
||||
}
|
||||
|
||||
q := fakeQueryable{}
|
||||
sqlstr, pp, err := BuildInsertStatement(q, "users", r{ID: "1", Name: "alice"})
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
tst.AssertTrue(t, strings.HasPrefix(sqlstr, "INSERT INTO users ("))
|
||||
tst.AssertTrue(t, strings.Contains(sqlstr, "id"))
|
||||
tst.AssertTrue(t, strings.Contains(sqlstr, "name"))
|
||||
tst.AssertEqual(t, 2, len(pp))
|
||||
|
||||
values := []any{}
|
||||
for _, v := range pp {
|
||||
values = append(values, v)
|
||||
}
|
||||
|
||||
hasID, hasName := false, false
|
||||
for _, v := range values {
|
||||
if vs, ok := v.(string); ok {
|
||||
if vs == "1" {
|
||||
hasID = true
|
||||
}
|
||||
if vs == "alice" {
|
||||
hasName = true
|
||||
}
|
||||
}
|
||||
}
|
||||
tst.AssertTrue(t, hasID)
|
||||
tst.AssertTrue(t, hasName)
|
||||
}
|
||||
|
||||
func TestBuildInsertStatementSkipsUnexported(t *testing.T) {
|
||||
type r struct {
|
||||
ID string `db:"id"`
|
||||
hidden string `db:"hidden"` //nolint:unused
|
||||
}
|
||||
|
||||
q := fakeQueryable{}
|
||||
sqlstr, pp, err := BuildInsertStatement(q, "users", r{ID: "1"})
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, 1, len(pp))
|
||||
tst.AssertTrue(t, !strings.Contains(sqlstr, "hidden"))
|
||||
}
|
||||
|
||||
func TestBuildInsertStatementSkipsNoTagAndDash(t *testing.T) {
|
||||
type r struct {
|
||||
ID string `db:"id"`
|
||||
Skip1 string `db:"-"`
|
||||
Skip2 string
|
||||
}
|
||||
|
||||
q := fakeQueryable{}
|
||||
sqlstr, pp, err := BuildInsertStatement(q, "users", r{ID: "1", Skip1: "x", Skip2: "y"})
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, 1, len(pp))
|
||||
tst.AssertTrue(t, !strings.Contains(sqlstr, "Skip"))
|
||||
}
|
||||
|
||||
func TestBuildInsertStatementNoFields(t *testing.T) {
|
||||
type r struct {
|
||||
Skip string
|
||||
}
|
||||
q := fakeQueryable{}
|
||||
_, _, err := BuildInsertStatement(q, "x", r{})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for no usable fields")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildInsertStatementNilPointer(t *testing.T) {
|
||||
type r struct {
|
||||
ID string `db:"id"`
|
||||
Note *string `db:"note"`
|
||||
}
|
||||
|
||||
q := fakeQueryable{}
|
||||
sqlstr, pp, err := BuildInsertStatement(q, "users", r{ID: "1", Note: nil})
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
// Only id is parameterized; nil pointer becomes literal NULL
|
||||
tst.AssertEqual(t, 1, len(pp))
|
||||
tst.AssertTrue(t, strings.Contains(sqlstr, "NULL"))
|
||||
}
|
||||
|
||||
func TestBuildInsertStatementWithConverter(t *testing.T) {
|
||||
type r struct {
|
||||
ID string `db:"id"`
|
||||
Flag bool `db:"flag"`
|
||||
}
|
||||
|
||||
q := fakeQueryable{converters: []DBTypeConverter{ConverterBoolToBit}}
|
||||
_, pp, err := BuildInsertStatement(q, "users", r{ID: "1", Flag: true})
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, 2, len(pp))
|
||||
|
||||
foundOne := false
|
||||
for _, v := range pp {
|
||||
if vi, ok := v.(int64); ok && vi == 1 {
|
||||
foundOne = true
|
||||
}
|
||||
}
|
||||
tst.AssertTrue(t, foundOne)
|
||||
}
|
||||
|
||||
func TestBuildUpdateStatementBasic(t *testing.T) {
|
||||
type r struct {
|
||||
ID string `db:"id"`
|
||||
Name string `db:"name"`
|
||||
}
|
||||
|
||||
q := fakeQueryable{}
|
||||
sqlstr, pp, err := BuildUpdateStatement(q, "users", r{ID: "1", Name: "alice"}, "id")
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
tst.AssertTrue(t, strings.HasPrefix(sqlstr, "UPDATE users SET "))
|
||||
tst.AssertTrue(t, strings.Contains(sqlstr, "name = :"))
|
||||
tst.AssertTrue(t, strings.Contains(sqlstr, "(id = :"))
|
||||
tst.AssertEqual(t, 2, len(pp))
|
||||
}
|
||||
|
||||
func TestBuildUpdateStatementMissingID(t *testing.T) {
|
||||
type r struct {
|
||||
Name string `db:"name"`
|
||||
}
|
||||
|
||||
q := fakeQueryable{}
|
||||
_, _, err := BuildUpdateStatement(q, "users", r{Name: "alice"}, "id")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for missing id column")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildUpdateStatementOnlyID(t *testing.T) {
|
||||
type r struct {
|
||||
ID string `db:"id"`
|
||||
}
|
||||
|
||||
q := fakeQueryable{}
|
||||
_, _, err := BuildUpdateStatement(q, "users", r{ID: "1"}, "id")
|
||||
if err == nil {
|
||||
t.Fatal("expected error when no SET clauses")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildUpdateStatementNilPointer(t *testing.T) {
|
||||
type r struct {
|
||||
ID string `db:"id"`
|
||||
Note *string `db:"note"`
|
||||
}
|
||||
|
||||
q := fakeQueryable{}
|
||||
sqlstr, _, err := BuildUpdateStatement(q, "users", r{ID: "1", Note: nil}, "id")
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertTrue(t, strings.Contains(sqlstr, "note = NULL"))
|
||||
}
|
||||
|
||||
func TestBuildInsertMultipleStatementBasic(t *testing.T) {
|
||||
type r struct {
|
||||
ID string `db:"id"`
|
||||
Name string `db:"name"`
|
||||
}
|
||||
|
||||
q := fakeQueryable{}
|
||||
sqlstr, pp, err := BuildInsertMultipleStatement(q, "users", []r{
|
||||
{ID: "1", Name: "alice"},
|
||||
{ID: "2", Name: "bob"},
|
||||
})
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
tst.AssertTrue(t, strings.Contains(sqlstr, `INSERT INTO "users"`))
|
||||
tst.AssertTrue(t, strings.Contains(sqlstr, `"id"`))
|
||||
tst.AssertTrue(t, strings.Contains(sqlstr, `"name"`))
|
||||
// 2 rows × 2 fields = 4 placeholders
|
||||
tst.AssertEqual(t, 4, len(pp))
|
||||
|
||||
// Two value tuples should appear -> exactly one "), (" separator
|
||||
tst.AssertEqual(t, 1, strings.Count(sqlstr, "), ("))
|
||||
}
|
||||
|
||||
func TestBuildInsertMultipleStatementEmpty(t *testing.T) {
|
||||
type r struct {
|
||||
ID string `db:"id"`
|
||||
}
|
||||
q := fakeQueryable{}
|
||||
_, _, err := BuildInsertMultipleStatement(q, "x", []r{})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for empty input")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildInsertMultipleStatementNilPointer(t *testing.T) {
|
||||
type r struct {
|
||||
ID string `db:"id"`
|
||||
Note *string `db:"note"`
|
||||
}
|
||||
|
||||
q := fakeQueryable{}
|
||||
sqlstr, _, err := BuildInsertMultipleStatement(q, "users", []r{{ID: "1", Note: nil}})
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertTrue(t, strings.Contains(sqlstr, "NULL"))
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package sq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/tst"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFnTrimCommentsLineOnly(t *testing.T) {
|
||||
// The line-only comment is replaced with an empty line.
|
||||
sql := "SELECT *\n-- this is a comment\nFROM users"
|
||||
pp := PP{}
|
||||
err := fnTrimComments(context.Background(), "QUERY", nil, &sql, &pp)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertTrue(t, !strings.Contains(sql, "comment"))
|
||||
tst.AssertTrue(t, strings.Contains(sql, "SELECT *"))
|
||||
tst.AssertTrue(t, strings.Contains(sql, "FROM users"))
|
||||
}
|
||||
|
||||
func TestFnTrimCommentsTrailing(t *testing.T) {
|
||||
sql := "SELECT * -- inline\nFROM users -- end"
|
||||
pp := PP{}
|
||||
err := fnTrimComments(context.Background(), "QUERY", nil, &sql, &pp)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, "SELECT *\nFROM users", sql)
|
||||
}
|
||||
|
||||
func TestFnTrimCommentsIndented(t *testing.T) {
|
||||
sql := "SELECT *\n -- indented comment\nFROM users"
|
||||
pp := PP{}
|
||||
err := fnTrimComments(context.Background(), "QUERY", nil, &sql, &pp)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertTrue(t, !strings.Contains(sql, "indented comment"))
|
||||
tst.AssertTrue(t, strings.Contains(sql, "SELECT *"))
|
||||
tst.AssertTrue(t, strings.Contains(sql, "FROM users"))
|
||||
}
|
||||
|
||||
func TestFnTrimCommentsTrimsTrailingWhitespace(t *testing.T) {
|
||||
sql := "SELECT * \t\nFROM users "
|
||||
pp := PP{}
|
||||
err := fnTrimComments(context.Background(), "QUERY", nil, &sql, &pp)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, "SELECT *\nFROM users", sql)
|
||||
}
|
||||
|
||||
func TestFnTrimCommentsNoComment(t *testing.T) {
|
||||
sql := "SELECT id\nFROM users\nWHERE id=1"
|
||||
pp := PP{}
|
||||
err := fnTrimComments(context.Background(), "QUERY", nil, &sql, &pp)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, "SELECT id\nFROM users\nWHERE id=1", sql)
|
||||
}
|
||||
|
||||
func TestFnTrimCommentsEmpty(t *testing.T) {
|
||||
sql := ""
|
||||
pp := PP{}
|
||||
err := fnTrimComments(context.Background(), "QUERY", nil, &sql, &pp)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, "", sql)
|
||||
}
|
||||
|
||||
func TestCommentTrimmerListener(t *testing.T) {
|
||||
sql := "SELECT *\n-- a comment\nFROM x"
|
||||
pp := PP{}
|
||||
err := CommentTrimmer.PreQuery(context.Background(), nil, &sql, &pp, PreQueryMeta{})
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertTrue(t, !strings.Contains(sql, "a comment"))
|
||||
|
||||
sql2 := "INSERT INTO x VALUES (1) -- xx"
|
||||
err = CommentTrimmer.PreExec(context.Background(), nil, &sql2, &pp, PreExecMeta{})
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, "INSERT INTO x VALUES (1)", sql2)
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package sq
|
||||
|
||||
import (
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/rfctime"
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/tst"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestConverterBoolToBit(t *testing.T) {
|
||||
v, err := ConverterBoolToBit.ModelToDB(true)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, int64(1), v.(int64))
|
||||
|
||||
v, err = ConverterBoolToBit.ModelToDB(false)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, int64(0), v.(int64))
|
||||
|
||||
v, err = ConverterBoolToBit.DBToModel(int64(1))
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, true, v.(bool))
|
||||
|
||||
v, err = ConverterBoolToBit.DBToModel(int64(0))
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, false, v.(bool))
|
||||
|
||||
_, err = ConverterBoolToBit.DBToModel(int64(2))
|
||||
if err == nil {
|
||||
t.Fatal("expected error for value not in {0,1}")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConverterTimeToUnixMillis(t *testing.T) {
|
||||
t0 := time.Date(2024, 6, 15, 12, 34, 56, int(789*time.Millisecond), time.UTC)
|
||||
expected := t0.UnixMilli()
|
||||
|
||||
v, err := ConverterTimeToUnixMillis.ModelToDB(t0)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, expected, v.(int64))
|
||||
|
||||
v, err = ConverterTimeToUnixMillis.DBToModel(expected)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, expected, v.(time.Time).UnixMilli())
|
||||
}
|
||||
|
||||
func TestConverterRFCUnixMilliTime(t *testing.T) {
|
||||
t0 := rfctime.NewUnixMilli(time.Date(2020, 1, 2, 3, 4, 5, 0, time.UTC))
|
||||
expected := t0.UnixMilli()
|
||||
|
||||
v, err := ConverterRFCUnixMilliTimeToUnixMillis.ModelToDB(t0)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, expected, v.(int64))
|
||||
|
||||
v, err = ConverterRFCUnixMilliTimeToUnixMillis.DBToModel(expected)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, expected, v.(rfctime.UnixMilliTime).UnixMilli())
|
||||
}
|
||||
|
||||
func TestConverterRFCUnixNanoTime(t *testing.T) {
|
||||
t0 := rfctime.NewUnixNano(time.Date(2020, 1, 2, 3, 4, 5, 123456789, time.UTC))
|
||||
expected := t0.UnixNano()
|
||||
|
||||
v, err := ConverterRFCUnixNanoTimeToUnixNanos.ModelToDB(t0)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, expected, v.(int64))
|
||||
|
||||
v, err = ConverterRFCUnixNanoTimeToUnixNanos.DBToModel(expected)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, expected, v.(rfctime.UnixNanoTime).UnixNano())
|
||||
}
|
||||
|
||||
func TestConverterRFCUnixTime(t *testing.T) {
|
||||
t0 := rfctime.NewUnix(time.Date(2020, 1, 2, 3, 4, 5, 0, time.UTC))
|
||||
expected := t0.Unix()
|
||||
|
||||
v, err := ConverterRFCUnixTimeToUnixSeconds.ModelToDB(t0)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, expected, v.(int64))
|
||||
|
||||
v, err = ConverterRFCUnixTimeToUnixSeconds.DBToModel(expected)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, expected, v.(rfctime.UnixTime).Unix())
|
||||
}
|
||||
|
||||
func TestConverterRFC339Time(t *testing.T) {
|
||||
t0 := rfctime.NewRFC3339(time.Date(2020, 6, 15, 9, 30, 45, 0, time.UTC))
|
||||
|
||||
v, err := ConverterRFC339TimeToString.ModelToDB(t0)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, "2020-06-15 09:30:45", v.(string))
|
||||
|
||||
v, err = ConverterRFC339TimeToString.DBToModel("2020-06-15 09:30:45")
|
||||
tst.AssertNoErr(t, err)
|
||||
tt := v.(rfctime.RFC3339Time).Time().UTC()
|
||||
tst.AssertEqual(t, 2020, tt.Year())
|
||||
tst.AssertEqual(t, time.June, tt.Month())
|
||||
tst.AssertEqual(t, 15, tt.Day())
|
||||
tst.AssertEqual(t, 9, tt.Hour())
|
||||
tst.AssertEqual(t, 30, tt.Minute())
|
||||
tst.AssertEqual(t, 45, tt.Second())
|
||||
|
||||
_, err = ConverterRFC339TimeToString.DBToModel("garbage")
|
||||
if err == nil {
|
||||
t.Fatal("expected parse error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConverterRFC339NanoTime(t *testing.T) {
|
||||
t0 := rfctime.NewRFC3339Nano(time.Date(2020, 6, 15, 9, 30, 45, 123456789, time.UTC))
|
||||
|
||||
v, err := ConverterRFC339NanoTimeToString.ModelToDB(t0)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, "2020-06-15 09:30:45.123456789", v.(string))
|
||||
|
||||
v, err = ConverterRFC339NanoTimeToString.DBToModel("2020-06-15 09:30:45.123456789")
|
||||
tst.AssertNoErr(t, err)
|
||||
tt := v.(rfctime.RFC3339NanoTime).Time().UTC()
|
||||
tst.AssertEqual(t, 123456789, tt.Nanosecond())
|
||||
|
||||
_, err = ConverterRFC339NanoTimeToString.DBToModel("not a date")
|
||||
if err == nil {
|
||||
t.Fatal("expected parse error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConverterRFCDate(t *testing.T) {
|
||||
d := rfctime.Date{Year: 2024, Month: 3, Day: 9}
|
||||
v, err := ConverterRFCDateToString.ModelToDB(d)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, "2024-03-09", v.(string))
|
||||
|
||||
v, err = ConverterRFCDateToString.DBToModel("2024-03-09")
|
||||
tst.AssertNoErr(t, err)
|
||||
d2 := v.(rfctime.Date)
|
||||
tst.AssertEqual(t, 2024, d2.Year)
|
||||
tst.AssertEqual(t, 3, d2.Month)
|
||||
tst.AssertEqual(t, 9, d2.Day)
|
||||
|
||||
_, err = ConverterRFCDateToString.DBToModel("invalid")
|
||||
if err == nil {
|
||||
t.Fatal("expected parse error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConverterRFCTime(t *testing.T) {
|
||||
tm := rfctime.NewTime(13, 30, 45, 0)
|
||||
v, err := ConverterRFCTimeToString.ModelToDB(tm)
|
||||
tst.AssertNoErr(t, err)
|
||||
roundtrip, err := ConverterRFCTimeToString.DBToModel(v.(string))
|
||||
tst.AssertNoErr(t, err)
|
||||
tm2 := roundtrip.(rfctime.Time)
|
||||
tst.AssertEqual(t, 13, tm2.Hour)
|
||||
tst.AssertEqual(t, 30, tm2.Minute)
|
||||
tst.AssertEqual(t, 45, tm2.Second)
|
||||
|
||||
_, err = ConverterRFCTimeToString.DBToModel("xx:xx:xx")
|
||||
if err == nil {
|
||||
t.Fatal("expected parse error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConverterRFCSecondsF64(t *testing.T) {
|
||||
d := 12*time.Second + 500*time.Millisecond
|
||||
s := rfctime.NewSecondsF64(d)
|
||||
v, err := ConverterRFCSecondsF64ToString.ModelToDB(s)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, 12.5, v.(float64))
|
||||
|
||||
v, err = ConverterRFCSecondsF64ToString.DBToModel(12.5)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, 12.5, v.(rfctime.SecondsF64).Seconds())
|
||||
}
|
||||
|
||||
func TestConverterJsonObjToString(t *testing.T) {
|
||||
tst.AssertEqual(t, "sq.JsonObj", ConverterJsonObjToString.ModelTypeString())
|
||||
tst.AssertEqual(t, "string", ConverterJsonObjToString.DBTypeString())
|
||||
|
||||
v, err := ConverterJsonObjToString.ModelToDB(JsonObj{"x": float64(1)})
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, `{"x":1}`, v.(string))
|
||||
|
||||
r, err := ConverterJsonObjToString.DBToModel(`{"x":1}`)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertStrRepEqual(t, r.(JsonObj)["x"], float64(1))
|
||||
}
|
||||
|
||||
func TestConverterJsonArrToString(t *testing.T) {
|
||||
tst.AssertEqual(t, "sq.JsonArr", ConverterJsonArrToString.ModelTypeString())
|
||||
tst.AssertEqual(t, "string", ConverterJsonArrToString.DBTypeString())
|
||||
|
||||
v, err := ConverterJsonArrToString.ModelToDB(JsonArr{float64(1), float64(2)})
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, `[1,2]`, v.(string))
|
||||
|
||||
r, err := ConverterJsonArrToString.DBToModel(`[1,2]`)
|
||||
tst.AssertNoErr(t, err)
|
||||
arr := r.(JsonArr)
|
||||
tst.AssertEqual(t, 2, len(arr))
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package sq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/tst"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type fakeQueryable struct {
|
||||
converters []DBTypeConverter
|
||||
}
|
||||
|
||||
func (f fakeQueryable) Exec(ctx context.Context, sqlstr string, prep PP) (sql.Result, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (f fakeQueryable) Query(ctx context.Context, sqlstr string, prep PP) (*sqlx.Rows, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (f fakeQueryable) ListConverter() []DBTypeConverter {
|
||||
return f.converters
|
||||
}
|
||||
|
||||
func TestNewDBTypeConverterTypeStrings(t *testing.T) {
|
||||
conv := NewDBTypeConverter(func(v bool) (int64, error) { return 0, nil }, func(v int64) (bool, error) { return false, nil })
|
||||
tst.AssertEqual(t, "bool", conv.ModelTypeString())
|
||||
tst.AssertEqual(t, "int64", conv.DBTypeString())
|
||||
}
|
||||
|
||||
func TestNewDBTypeConverterModelToDB(t *testing.T) {
|
||||
conv := NewDBTypeConverter(func(v bool) (int64, error) {
|
||||
if v {
|
||||
return 1, nil
|
||||
}
|
||||
return 0, nil
|
||||
}, func(v int64) (bool, error) {
|
||||
return v != 0, nil
|
||||
})
|
||||
|
||||
r, err := conv.ModelToDB(true)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, int64(1), r.(int64))
|
||||
|
||||
r, err = conv.ModelToDB(false)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, int64(0), r.(int64))
|
||||
}
|
||||
|
||||
func TestNewDBTypeConverterDBToModel(t *testing.T) {
|
||||
conv := NewDBTypeConverter(func(v bool) (int64, error) { return 0, nil }, func(v int64) (bool, error) {
|
||||
return v != 0, nil
|
||||
})
|
||||
|
||||
r, err := conv.DBToModel(int64(1))
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, true, r.(bool))
|
||||
|
||||
r, err = conv.DBToModel(int64(0))
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, false, r.(bool))
|
||||
}
|
||||
|
||||
func TestNewDBTypeConverterTypeMismatch(t *testing.T) {
|
||||
conv := NewDBTypeConverter(func(v bool) (int64, error) { return 0, nil }, func(v int64) (bool, error) { return false, nil })
|
||||
|
||||
_, err := conv.ModelToDB("not a bool")
|
||||
if err == nil {
|
||||
t.Fatal("expected error on type mismatch in ModelToDB")
|
||||
}
|
||||
|
||||
_, err = conv.DBToModel("not int64")
|
||||
if err == nil {
|
||||
t.Fatal("expected error on type mismatch in DBToModel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAutoDBTypeConverter(t *testing.T) {
|
||||
conv := NewAutoDBTypeConverter(JsonObj{})
|
||||
|
||||
tst.AssertEqual(t, "sq.JsonObj", conv.ModelTypeString())
|
||||
tst.AssertEqual(t, "string", conv.DBTypeString())
|
||||
|
||||
r, err := conv.ModelToDB(JsonObj{"k": "v"})
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, `{"k":"v"}`, r.(string))
|
||||
|
||||
r, err = conv.DBToModel(`{"k":"v"}`)
|
||||
tst.AssertNoErr(t, err)
|
||||
parsed, ok := r.(JsonObj)
|
||||
tst.AssertTrue(t, ok)
|
||||
tst.AssertStrRepEqual(t, parsed["k"], "v")
|
||||
}
|
||||
|
||||
func TestConvertValueToDBNoConverter(t *testing.T) {
|
||||
q := fakeQueryable{}
|
||||
|
||||
r, err := convertValueToDB(q, "hello")
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, "hello", r.(string))
|
||||
|
||||
r, err = convertValueToDB(q, 42)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, 42, r.(int))
|
||||
}
|
||||
|
||||
func TestConvertValueToDBWithConverter(t *testing.T) {
|
||||
q := fakeQueryable{converters: []DBTypeConverter{ConverterBoolToBit}}
|
||||
|
||||
r, err := convertValueToDB(q, true)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, int64(1), r.(int64))
|
||||
|
||||
r, err = convertValueToDB(q, false)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, int64(0), r.(int64))
|
||||
}
|
||||
|
||||
func TestConvertValueToDBNilPointer(t *testing.T) {
|
||||
q := fakeQueryable{}
|
||||
|
||||
var s *string
|
||||
r, err := convertValueToDB(q, s)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, true, r == nil)
|
||||
}
|
||||
|
||||
func TestConvertValueToDBNonNilPointer(t *testing.T) {
|
||||
q := fakeQueryable{converters: []DBTypeConverter{ConverterBoolToBit}}
|
||||
|
||||
v := true
|
||||
r, err := convertValueToDB(q, &v)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, int64(1), r.(int64))
|
||||
}
|
||||
|
||||
func TestConvertValueToModelNoConverter(t *testing.T) {
|
||||
q := fakeQueryable{}
|
||||
|
||||
r, err := convertValueToModel(q, "hello", "string")
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, "hello", r.(string))
|
||||
}
|
||||
|
||||
func TestConvertValueToModelWithConverter(t *testing.T) {
|
||||
q := fakeQueryable{converters: []DBTypeConverter{ConverterBoolToBit}}
|
||||
|
||||
r, err := convertValueToModel(q, int64(1), "bool")
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, true, r.(bool))
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package sq
|
||||
|
||||
import (
|
||||
ct "git.blackforestbytes.com/BlackForestBytes/goext/cursortoken"
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/tst"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewEmptyPaginateFilter(t *testing.T) {
|
||||
f := NewEmptyPaginateFilter()
|
||||
|
||||
pp := PP{}
|
||||
flt, join, joinTbl := f.SQL(pp)
|
||||
tst.AssertEqual(t, "1=1", flt)
|
||||
tst.AssertEqual(t, "", join)
|
||||
tst.AssertEqual(t, 0, len(joinTbl))
|
||||
tst.AssertEqual(t, 0, len(pp))
|
||||
|
||||
tst.AssertEqual(t, 0, len(f.Sort()))
|
||||
}
|
||||
|
||||
func TestNewSimplePaginateFilter(t *testing.T) {
|
||||
sortOrder := []FilterSort{
|
||||
{Field: "name", Direction: ct.SortASC},
|
||||
}
|
||||
filterParams := PP{"p1": "value1"}
|
||||
|
||||
f := NewSimplePaginateFilter("name = :p1", filterParams, sortOrder)
|
||||
|
||||
pp := PP{}
|
||||
flt, join, joinTbl := f.SQL(pp)
|
||||
tst.AssertEqual(t, "name = :p1", flt)
|
||||
tst.AssertEqual(t, "", join)
|
||||
tst.AssertEqual(t, 0, len(joinTbl))
|
||||
// filterParams should be merged into pp
|
||||
tst.AssertEqual(t, 1, len(pp))
|
||||
tst.AssertEqual(t, "value1", pp["p1"])
|
||||
|
||||
srt := f.Sort()
|
||||
tst.AssertEqual(t, 1, len(srt))
|
||||
tst.AssertEqual(t, "name", srt[0].Field)
|
||||
tst.AssertEqual(t, ct.SortASC, srt[0].Direction)
|
||||
}
|
||||
|
||||
func TestNewPaginateFilter(t *testing.T) {
|
||||
sortOrder := []FilterSort{
|
||||
{Field: "id", Direction: ct.SortDESC},
|
||||
}
|
||||
|
||||
called := 0
|
||||
f := NewPaginateFilter(func(params PP) (string, string, []string) {
|
||||
called++
|
||||
params.Add("hello")
|
||||
return "id > 0", "JOIN other ON other.id = main.id", []string{"other"}
|
||||
}, sortOrder)
|
||||
|
||||
pp := PP{}
|
||||
flt, join, joinTbl := f.SQL(pp)
|
||||
tst.AssertEqual(t, "id > 0", flt)
|
||||
tst.AssertEqual(t, "JOIN other ON other.id = main.id", join)
|
||||
tst.AssertEqual(t, 1, len(joinTbl))
|
||||
tst.AssertEqual(t, "other", joinTbl[0])
|
||||
tst.AssertEqual(t, 1, called)
|
||||
tst.AssertEqual(t, 1, len(pp))
|
||||
|
||||
srt := f.Sort()
|
||||
tst.AssertEqual(t, 1, len(srt))
|
||||
tst.AssertEqual(t, "id", srt[0].Field)
|
||||
tst.AssertEqual(t, ct.SortDESC, srt[0].Direction)
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package sq
|
||||
|
||||
import (
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/tst"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJsonObjMarshalToDB(t *testing.T) {
|
||||
j := JsonObj{}
|
||||
out, err := j.MarshalToDB(JsonObj{"key": "value", "num": float64(7)})
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
// JSON map ordering is not guaranteed - parse and verify
|
||||
roundtrip, err := j.UnmarshalToModel(out)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertStrRepEqual(t, roundtrip["key"], "value")
|
||||
tst.AssertStrRepEqual(t, roundtrip["num"], float64(7))
|
||||
}
|
||||
|
||||
func TestJsonObjUnmarshalToModelInvalid(t *testing.T) {
|
||||
j := JsonObj{}
|
||||
_, err := j.UnmarshalToModel("{not valid json}")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid JSON")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonArrMarshalToDB(t *testing.T) {
|
||||
j := JsonArr{}
|
||||
out, err := j.MarshalToDB(JsonArr{float64(1), "two", true})
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
tst.AssertEqual(t, `[1,"two",true]`, out)
|
||||
}
|
||||
|
||||
func TestJsonArrRoundtrip(t *testing.T) {
|
||||
j := JsonArr{}
|
||||
out, err := j.MarshalToDB(JsonArr{"a", "b", "c"})
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
r, err := j.UnmarshalToModel(out)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, 3, len(r))
|
||||
tst.AssertEqual(t, "a", r[0])
|
||||
tst.AssertEqual(t, "b", r[1])
|
||||
tst.AssertEqual(t, "c", r[2])
|
||||
}
|
||||
|
||||
func TestJsonArrUnmarshalInvalid(t *testing.T) {
|
||||
j := JsonArr{}
|
||||
_, err := j.UnmarshalToModel("not json")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid JSON")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoJsonRoundtrip(t *testing.T) {
|
||||
type inner struct {
|
||||
A int `json:"a"`
|
||||
B string `json:"b"`
|
||||
}
|
||||
|
||||
aj := AutoJson[inner]{Value: inner{A: 42, B: "foo"}}
|
||||
zero := AutoJson[inner]{}
|
||||
|
||||
out, err := zero.MarshalToDB(aj)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, `{"a":42,"b":"foo"}`, out)
|
||||
|
||||
r, err := zero.UnmarshalToModel(out)
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, 42, r.Value.A)
|
||||
tst.AssertEqual(t, "foo", r.Value.B)
|
||||
}
|
||||
|
||||
func TestAutoJsonUnmarshalInvalid(t *testing.T) {
|
||||
type inner struct {
|
||||
A int `json:"a"`
|
||||
}
|
||||
zero := AutoJson[inner]{}
|
||||
_, err := zero.UnmarshalToModel("garbage")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid JSON")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
package sq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/tst"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewPrePingListener(t *testing.T) {
|
||||
called := 0
|
||||
expected := errors.New("ping err")
|
||||
l := NewPrePingListener(func(ctx context.Context, meta PrePingMeta) error {
|
||||
called++
|
||||
return expected
|
||||
})
|
||||
|
||||
err := l.PrePing(context.Background(), PrePingMeta{})
|
||||
tst.AssertEqual(t, 1, called)
|
||||
tst.AssertTrue(t, errors.Is(err, expected))
|
||||
|
||||
// Other handlers must not error / panic
|
||||
tst.AssertNoErr(t, l.PreTxBegin(context.Background(), 0, PreTxBeginMeta{}))
|
||||
tst.AssertNoErr(t, l.PreTxCommit(0, PreTxCommitMeta{}))
|
||||
tst.AssertNoErr(t, l.PreTxRollback(0, PreTxRollbackMeta{}))
|
||||
tst.AssertNoErr(t, l.PreQuery(context.Background(), nil, nil, nil, PreQueryMeta{}))
|
||||
tst.AssertNoErr(t, l.PreExec(context.Background(), nil, nil, nil, PreExecMeta{}))
|
||||
|
||||
// Post variants must not panic
|
||||
l.PostPing(nil, PostPingMeta{})
|
||||
l.PostTxBegin(0, nil, PostTxBeginMeta{})
|
||||
l.PostTxCommit(0, nil, PostTxCommitMeta{})
|
||||
l.PostTxRollback(0, nil, PostTxRollbackMeta{})
|
||||
l.PostQuery(nil, "", "", PP{}, nil, PostQueryMeta{})
|
||||
l.PostExec(nil, "", "", PP{}, nil, PostExecMeta{})
|
||||
}
|
||||
|
||||
func TestNewPreTxBeginListener(t *testing.T) {
|
||||
called := 0
|
||||
l := NewPreTxBeginListener(func(ctx context.Context, txid uint16, meta PreTxBeginMeta) error {
|
||||
called++
|
||||
tst.AssertEqual(t, uint16(7), txid)
|
||||
return nil
|
||||
})
|
||||
tst.AssertNoErr(t, l.PreTxBegin(context.Background(), 7, PreTxBeginMeta{}))
|
||||
tst.AssertEqual(t, 1, called)
|
||||
}
|
||||
|
||||
func TestNewPreTxCommitListener(t *testing.T) {
|
||||
called := 0
|
||||
l := NewPreTxCommitListener(func(txid uint16, meta PreTxCommitMeta) error {
|
||||
called++
|
||||
return nil
|
||||
})
|
||||
tst.AssertNoErr(t, l.PreTxCommit(0, PreTxCommitMeta{}))
|
||||
tst.AssertEqual(t, 1, called)
|
||||
}
|
||||
|
||||
func TestNewPreTxRollbackListener(t *testing.T) {
|
||||
called := 0
|
||||
l := NewPreTxRollbackListener(func(txid uint16, meta PreTxRollbackMeta) error {
|
||||
called++
|
||||
return nil
|
||||
})
|
||||
tst.AssertNoErr(t, l.PreTxRollback(0, PreTxRollbackMeta{}))
|
||||
tst.AssertEqual(t, 1, called)
|
||||
}
|
||||
|
||||
func TestNewPreQueryListener(t *testing.T) {
|
||||
called := 0
|
||||
l := NewPreQueryListener(func(ctx context.Context, txID *uint16, sql *string, params *PP, meta PreQueryMeta) error {
|
||||
called++
|
||||
*sql = "modified"
|
||||
return nil
|
||||
})
|
||||
|
||||
sql := "original"
|
||||
pp := PP{}
|
||||
tst.AssertNoErr(t, l.PreQuery(context.Background(), nil, &sql, &pp, PreQueryMeta{}))
|
||||
tst.AssertEqual(t, 1, called)
|
||||
tst.AssertEqual(t, "modified", sql)
|
||||
}
|
||||
|
||||
func TestNewPreExecListener(t *testing.T) {
|
||||
called := 0
|
||||
l := NewPreExecListener(func(ctx context.Context, txID *uint16, sql *string, params *PP, meta PreExecMeta) error {
|
||||
called++
|
||||
return nil
|
||||
})
|
||||
sql := "x"
|
||||
pp := PP{}
|
||||
tst.AssertNoErr(t, l.PreExec(context.Background(), nil, &sql, &pp, PreExecMeta{}))
|
||||
tst.AssertEqual(t, 1, called)
|
||||
}
|
||||
|
||||
func TestNewPreListenerBoth(t *testing.T) {
|
||||
queryCalls := 0
|
||||
execCalls := 0
|
||||
l := NewPreListener(func(ctx context.Context, cmdtype string, txID *uint16, sql *string, params *PP) error {
|
||||
switch cmdtype {
|
||||
case "QUERY":
|
||||
queryCalls++
|
||||
case "EXEC":
|
||||
execCalls++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
sql := "s"
|
||||
pp := PP{}
|
||||
tst.AssertNoErr(t, l.PreQuery(context.Background(), nil, &sql, &pp, PreQueryMeta{}))
|
||||
tst.AssertNoErr(t, l.PreExec(context.Background(), nil, &sql, &pp, PreExecMeta{}))
|
||||
|
||||
tst.AssertEqual(t, 1, queryCalls)
|
||||
tst.AssertEqual(t, 1, execCalls)
|
||||
}
|
||||
|
||||
func TestNewPostPingListener(t *testing.T) {
|
||||
called := 0
|
||||
l := NewPostPingListener(func(result error, meta PostPingMeta) {
|
||||
called++
|
||||
})
|
||||
l.PostPing(nil, PostPingMeta{})
|
||||
tst.AssertEqual(t, 1, called)
|
||||
}
|
||||
|
||||
func TestNewPostTxBeginListener(t *testing.T) {
|
||||
called := 0
|
||||
l := NewPostTxBeginListener(func(txid uint16, result error, meta PostTxBeginMeta) {
|
||||
called++
|
||||
})
|
||||
l.PostTxBegin(0, nil, PostTxBeginMeta{})
|
||||
tst.AssertEqual(t, 1, called)
|
||||
}
|
||||
|
||||
func TestNewPostTxCommitListener(t *testing.T) {
|
||||
called := 0
|
||||
l := NewPostTxCommitListener(func(txid uint16, result error, meta PostTxCommitMeta) {
|
||||
called++
|
||||
})
|
||||
l.PostTxCommit(0, nil, PostTxCommitMeta{})
|
||||
tst.AssertEqual(t, 1, called)
|
||||
}
|
||||
|
||||
func TestNewPostTxRollbackListener(t *testing.T) {
|
||||
called := 0
|
||||
l := NewPostTxRollbackListener(func(txid uint16, result error, meta PostTxRollbackMeta) {
|
||||
called++
|
||||
})
|
||||
l.PostTxRollback(0, nil, PostTxRollbackMeta{})
|
||||
tst.AssertEqual(t, 1, called)
|
||||
}
|
||||
|
||||
func TestNewPostQueryListener(t *testing.T) {
|
||||
called := 0
|
||||
l := NewPostQueryListener(func(txID *uint16, sqlOriginal string, sqlReal string, params PP, result error, meta PostQueryMeta) {
|
||||
called++
|
||||
})
|
||||
l.PostQuery(nil, "", "", PP{}, nil, PostQueryMeta{})
|
||||
tst.AssertEqual(t, 1, called)
|
||||
}
|
||||
|
||||
func TestNewPostExecListener(t *testing.T) {
|
||||
called := 0
|
||||
l := NewPostExecListener(func(txID *uint16, sqlOriginal string, sqlReal string, params PP, result error, meta PostExecMeta) {
|
||||
called++
|
||||
})
|
||||
l.PostExec(nil, "", "", PP{}, nil, PostExecMeta{})
|
||||
tst.AssertEqual(t, 1, called)
|
||||
}
|
||||
|
||||
func TestNewPostListenerBoth(t *testing.T) {
|
||||
queryCalls := 0
|
||||
execCalls := 0
|
||||
l := NewPostListener(func(cmdtype string, txID *uint16, sqlOriginal string, sqlReal string, result error, params PP) {
|
||||
switch cmdtype {
|
||||
case "QUERY":
|
||||
queryCalls++
|
||||
case "EXEC":
|
||||
execCalls++
|
||||
}
|
||||
})
|
||||
l.PostQuery(nil, "", "", PP{}, nil, PostQueryMeta{})
|
||||
l.PostExec(nil, "", "", PP{}, nil, PostExecMeta{})
|
||||
tst.AssertEqual(t, 1, queryCalls)
|
||||
tst.AssertEqual(t, 1, execCalls)
|
||||
}
|
||||
|
||||
func TestGenListenerNilHandlersDontPanic(t *testing.T) {
|
||||
// A listener constructed with one constructor only sets one handler.
|
||||
// Calls to other handlers should be safe no-ops.
|
||||
l := NewPostPingListener(func(result error, meta PostPingMeta) {})
|
||||
|
||||
// All Pre* return nil
|
||||
tst.AssertNoErr(t, l.PrePing(context.Background(), PrePingMeta{}))
|
||||
tst.AssertNoErr(t, l.PreTxBegin(context.Background(), 0, PreTxBeginMeta{}))
|
||||
tst.AssertNoErr(t, l.PreTxCommit(0, PreTxCommitMeta{}))
|
||||
tst.AssertNoErr(t, l.PreTxRollback(0, PreTxRollbackMeta{}))
|
||||
tst.AssertNoErr(t, l.PreQuery(context.Background(), nil, nil, nil, PreQueryMeta{}))
|
||||
tst.AssertNoErr(t, l.PreExec(context.Background(), nil, nil, nil, PreExecMeta{}))
|
||||
|
||||
// All Post* are no-ops
|
||||
l.PostTxBegin(0, nil, PostTxBeginMeta{})
|
||||
l.PostTxCommit(0, nil, PostTxCommitMeta{})
|
||||
l.PostTxRollback(0, nil, PostTxRollbackMeta{})
|
||||
l.PostQuery(nil, "", "", PP{}, nil, PostQueryMeta{})
|
||||
l.PostExec(nil, "", "", PP{}, nil, PostExecMeta{})
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package sq
|
||||
|
||||
import (
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/tst"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPPID(t *testing.T) {
|
||||
id := PPID()
|
||||
tst.AssertTrue(t, strings.HasPrefix(id, "p_"))
|
||||
if len(id) != 2+8 {
|
||||
t.Errorf("expected length 10, got %d (id=%q)", len(id), id)
|
||||
}
|
||||
|
||||
// uniqueness - very high probability with 8 base62 chars
|
||||
seen := map[string]bool{}
|
||||
for range 1000 {
|
||||
x := PPID()
|
||||
if seen[x] {
|
||||
t.Errorf("duplicate PPID: %s", x)
|
||||
}
|
||||
seen[x] = true
|
||||
}
|
||||
}
|
||||
|
||||
func TestPPAdd(t *testing.T) {
|
||||
pp := PP{}
|
||||
id1 := pp.Add(123)
|
||||
id2 := pp.Add("hello")
|
||||
|
||||
tst.AssertNotEqual(t, id1, id2)
|
||||
tst.AssertEqual(t, 123, pp[id1])
|
||||
tst.AssertEqual(t, "hello", pp[id2])
|
||||
tst.AssertEqual(t, 2, len(pp))
|
||||
}
|
||||
|
||||
func TestPPAddAll(t *testing.T) {
|
||||
a := PP{"a": 1, "b": 2}
|
||||
b := PP{"c": 3, "d": 4}
|
||||
a.AddAll(b)
|
||||
|
||||
tst.AssertEqual(t, 4, len(a))
|
||||
tst.AssertEqual(t, 1, a["a"])
|
||||
tst.AssertEqual(t, 2, a["b"])
|
||||
tst.AssertEqual(t, 3, a["c"])
|
||||
tst.AssertEqual(t, 4, a["d"])
|
||||
}
|
||||
|
||||
func TestPPAddAllOverwrite(t *testing.T) {
|
||||
a := PP{"a": 1, "b": 2}
|
||||
b := PP{"a": 99}
|
||||
a.AddAll(b)
|
||||
|
||||
tst.AssertEqual(t, 2, len(a))
|
||||
tst.AssertEqual(t, 99, a["a"])
|
||||
tst.AssertEqual(t, 2, a["b"])
|
||||
}
|
||||
|
||||
func TestJoin(t *testing.T) {
|
||||
a := PP{"a": 1, "b": 2}
|
||||
b := PP{"c": 3}
|
||||
c := PP{"d": 4, "a": 99}
|
||||
|
||||
r := Join(a, b, c)
|
||||
|
||||
tst.AssertEqual(t, 4, len(r))
|
||||
tst.AssertEqual(t, 99, r["a"])
|
||||
tst.AssertEqual(t, 2, r["b"])
|
||||
tst.AssertEqual(t, 3, r["c"])
|
||||
tst.AssertEqual(t, 4, r["d"])
|
||||
|
||||
// Source maps must remain unchanged
|
||||
tst.AssertEqual(t, 2, len(a))
|
||||
tst.AssertEqual(t, 1, a["a"])
|
||||
}
|
||||
|
||||
func TestJoinEmpty(t *testing.T) {
|
||||
r := Join()
|
||||
tst.AssertEqual(t, 0, len(r))
|
||||
}
|
||||
|
||||
func TestJoinSingle(t *testing.T) {
|
||||
a := PP{"a": 1}
|
||||
r := Join(a)
|
||||
tst.AssertEqual(t, 1, len(r))
|
||||
tst.AssertEqual(t, 1, r["a"])
|
||||
}
|
||||
Reference in New Issue
Block a user