mirror of
https://github.com/boolean-maybe/tiki
synced 2026-04-21 13:37:20 +00:00
improve coverage
This commit is contained in:
parent
f99a76fa56
commit
d78b187ac0
5 changed files with 766 additions and 0 deletions
|
|
@ -2,6 +2,7 @@ package runtime
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -419,3 +420,81 @@ func nonEmptyLines(s string) []string {
|
|||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func TestTableFormatterWriteError(t *testing.T) {
|
||||
proj := &ruki.TaskProjection{
|
||||
Fields: []string{"id"},
|
||||
Tasks: []*task.Task{{ID: "TIKI-ABC123"}},
|
||||
}
|
||||
|
||||
ew := &errorWriter{failAfter: 0}
|
||||
err := NewTableFormatter().Format(ew, proj)
|
||||
if err == nil {
|
||||
t.Fatal("expected write error")
|
||||
}
|
||||
}
|
||||
|
||||
type errorWriter struct {
|
||||
writes int
|
||||
failAfter int
|
||||
}
|
||||
|
||||
func (w *errorWriter) Write(p []byte) (int, error) {
|
||||
if w.writes >= w.failAfter {
|
||||
return 0, fmt.Errorf("write error")
|
||||
}
|
||||
w.writes++
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func TestRenderListNilSlice(t *testing.T) {
|
||||
got := renderList(nil)
|
||||
if got != "" {
|
||||
t.Errorf("expected empty for nil, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderListNonStringSlice(t *testing.T) {
|
||||
got := renderList(42)
|
||||
if got != "" {
|
||||
t.Errorf("expected empty for non-slice, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderIntNonInt(t *testing.T) {
|
||||
got := renderInt("not an int")
|
||||
if got != "" {
|
||||
t.Errorf("expected empty for non-int, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderDateNonTime(t *testing.T) {
|
||||
got := renderDate("not a time")
|
||||
if got != "" {
|
||||
t.Errorf("expected empty for non-time, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderTimestampNonTime(t *testing.T) {
|
||||
got := renderTimestamp("not a time")
|
||||
if got != "" {
|
||||
t.Errorf("expected empty for non-time, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEscapeScalar(t *testing.T) {
|
||||
tests := []struct {
|
||||
input, want string
|
||||
}{
|
||||
{"hello", "hello"},
|
||||
{"line\nnewline", `line\nnewline`},
|
||||
{"tab\there", `tab\there`},
|
||||
{`back\slash`, `back\\slash`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := escapeScalar(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("escapeScalar(%q) = %q, want %q", tt.input, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,3 +140,10 @@ func TestMapValueTypeCompleteness(t *testing.T) {
|
|||
seen[rv] = wt
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapValueTypeUnknownFallback(t *testing.T) {
|
||||
got := mapValueType(workflow.ValueType(999))
|
||||
if got != ruki.ValueString {
|
||||
t.Errorf("expected fallback to ValueString, got %d", got)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -203,3 +203,19 @@ func TestKeywordInIdentPosition_ParseError(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsReservedKeyword(t *testing.T) {
|
||||
reserved := []string{"select", "SELECT", "where", "create", "update", "delete", "and", "or", "not", "in", "is", "empty", "any", "all", "set", "order", "by", "asc", "desc", "before", "after", "deny", "run", "old", "new"}
|
||||
for _, kw := range reserved {
|
||||
if !IsReservedKeyword(kw) {
|
||||
t.Errorf("expected %q to be reserved", kw)
|
||||
}
|
||||
}
|
||||
|
||||
notReserved := []string{"title", "status", "foo", "bar", "hello"}
|
||||
for _, kw := range notReserved {
|
||||
if IsReservedKeyword(kw) {
|
||||
t.Errorf("expected %q to NOT be reserved", kw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -461,6 +461,7 @@ func TestUnquoteString(t *testing.T) {
|
|||
{"no quotes", "bare", "bare"},
|
||||
{"empty quotes", `""`, ""},
|
||||
{"single char", `"x"`, "x"},
|
||||
{"invalid escape fallback", "\"bad\\qescape\"", "bad\\qescape"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
@ -472,3 +473,242 @@ func TestUnquoteString(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestLower_EmptyStatement exercises the default branch of lowerStatement.
|
||||
func TestLower_EmptyStatement(t *testing.T) {
|
||||
_, err := lowerStatement(&statementGrammar{})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for empty statement grammar")
|
||||
}
|
||||
if err.Error() != "empty statement" {
|
||||
t.Errorf("expected 'empty statement', got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLower_EmptyTriggerAction exercises the default branch of lowerTriggerAction.
|
||||
func TestLower_EmptyTriggerAction(t *testing.T) {
|
||||
trig := &Trigger{Timing: "after", Event: "update"}
|
||||
err := lowerTriggerAction(&actionGrammar{}, trig)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for empty trigger action")
|
||||
}
|
||||
if err.Error() != "empty trigger action" {
|
||||
t.Errorf("expected 'empty trigger action', got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLower_EmptyExpression exercises the default branch of lowerUnary.
|
||||
func TestLower_EmptyExpression(t *testing.T) {
|
||||
_, err := lowerUnary(&unaryExpr{})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for empty expression")
|
||||
}
|
||||
if err.Error() != "empty expression" {
|
||||
t.Errorf("expected 'empty expression', got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLower_InvalidDateLiteral exercises parseDateLiteral error path.
|
||||
func TestLower_InvalidDateLiteral(t *testing.T) {
|
||||
_, err := parseDateLiteral("not-a-date")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid date literal")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLower_InvalidDurationLiteral exercises parseDurationLiteral error paths.
|
||||
func TestLower_InvalidDurationLiteral(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
}{
|
||||
{"no digits", "days"},
|
||||
{"no unit", "123"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := parseDurationLiteral(tt.input)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid duration")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestLower_SubQueryOrderByRejected exercises the order by rejection in lowerSubQuery.
|
||||
func TestLower_SubQueryOrderByRejected(t *testing.T) {
|
||||
dir := "asc"
|
||||
sq := &subQueryExpr{
|
||||
OrderBy: &orderByGrammar{
|
||||
First: orderByField{Field: "priority", Direction: &dir},
|
||||
},
|
||||
}
|
||||
_, err := lowerSubQuery(sq)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for order by in subquery")
|
||||
}
|
||||
if err.Error() != "order by is not valid inside a subquery" {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLower_ExprCondBareExpression exercises the default branch of lowerExprCond.
|
||||
func TestLower_ExprCondBareExpression(t *testing.T) {
|
||||
field := "title"
|
||||
ec := &exprCond{
|
||||
Left: exprGrammar{
|
||||
Left: unaryExpr{FieldRef: &field},
|
||||
},
|
||||
}
|
||||
_, err := lowerExprCond(ec)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for bare expression as condition")
|
||||
}
|
||||
if err.Error() != "expression used as condition without comparison operator" {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLower_TriggerRunAction exercises the run action branch in lowerTriggerAction.
|
||||
func TestLower_TriggerRunAction(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
trig, err := p.ParseTrigger(`after update run("echo done")`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if trig.Run == nil {
|
||||
t.Fatal("expected non-nil run action")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLower_TriggerUpdateAction exercises the update action branch in lowerTriggerAction.
|
||||
func TestLower_TriggerUpdateAction(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
trig, err := p.ParseTrigger(`after update where new.status = "done" update where id = new.id set priority=1`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if trig.Action == nil || trig.Action.Update == nil {
|
||||
t.Fatal("expected update action in trigger")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLower_TriggerDeleteAction exercises the delete action branch in lowerTriggerAction.
|
||||
func TestLower_TriggerDeleteAction(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
trig, err := p.ParseTrigger(`after update where new.status = "done" delete where id = new.id`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if trig.Action == nil || trig.Action.Delete == nil {
|
||||
t.Fatal("expected delete action in trigger")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLower_TriggerCreateAction exercises the create action branch in lowerTriggerAction.
|
||||
func TestLower_TriggerCreateAction(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
trig, err := p.ParseTrigger(`after update where new.status = "done" create title="follow-up"`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if trig.Action == nil || trig.Action.Create == nil {
|
||||
t.Fatal("expected create action in trigger")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLower_TriggerWithoutWhereOrAction exercises trigger lowering with no where and no action.
|
||||
func TestLower_TriggerWithoutWhereOrAction(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
trig, err := p.ParseTrigger(`before delete deny "forbidden"`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if trig.Where != nil {
|
||||
t.Error("expected nil where")
|
||||
}
|
||||
if trig.Deny == nil {
|
||||
t.Fatal("expected non-nil deny")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLower_DeleteStatement exercises the delete lowering path.
|
||||
func TestLower_DeleteStatement(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
stmt, err := p.ParseStatement(`delete where status = "done"`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if stmt.Delete == nil {
|
||||
t.Fatal("expected delete statement")
|
||||
}
|
||||
if stmt.Delete.Where == nil {
|
||||
t.Fatal("expected non-nil where in delete")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLower_UpdateStatement exercises the update lowering path.
|
||||
func TestLower_UpdateStatement(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
stmt, err := p.ParseStatement(`update where status = "done" set priority=1`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if stmt.Update == nil {
|
||||
t.Fatal("expected update statement")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLower_CreateStatement exercises the create lowering path.
|
||||
func TestLower_CreateStatement(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
stmt, err := p.ParseStatement(`create title="hello"`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if stmt.Create == nil {
|
||||
t.Fatal("expected create statement")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLower_OrCondChain exercises the or condition chaining in lowerOrCond.
|
||||
func TestLower_OrCondChain(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
stmt, err := p.ParseStatement(`select where status = "done" or status = "ready" or status = "backlog"`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
bc, ok := stmt.Select.Where.(*BinaryCondition)
|
||||
if !ok {
|
||||
t.Fatalf("expected BinaryCondition, got %T", stmt.Select.Where)
|
||||
}
|
||||
if bc.Op != "or" {
|
||||
t.Errorf("expected 'or', got %q", bc.Op)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLower_AndCondChain exercises the and condition chaining in lowerAndCond.
|
||||
func TestLower_AndCondChain(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
stmt, err := p.ParseStatement(`select where priority = 1 and status = "done" and assignee = "bob"`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
bc, ok := stmt.Select.Where.(*BinaryCondition)
|
||||
if !ok {
|
||||
t.Fatalf("expected BinaryCondition, got %T", stmt.Select.Where)
|
||||
}
|
||||
if bc.Op != "and" {
|
||||
t.Errorf("expected 'and', got %q", bc.Op)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1671,3 +1671,427 @@ func TestValidation_ListAssignmentRejectsFieldRefs(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_TypeNameCoverage(t *testing.T) {
|
||||
tests := []struct {
|
||||
typ ValueType
|
||||
want string
|
||||
}{
|
||||
{ValueString, "string"},
|
||||
{ValueInt, "int"},
|
||||
{ValueDate, "date"},
|
||||
{ValueTimestamp, "timestamp"},
|
||||
{ValueDuration, "duration"},
|
||||
{ValueBool, "bool"},
|
||||
{ValueID, "id"},
|
||||
{ValueRef, "ref"},
|
||||
{ValueRecurrence, "recurrence"},
|
||||
{ValueListString, "list<string>"},
|
||||
{ValueListRef, "list<ref>"},
|
||||
{ValueStatus, "status"},
|
||||
{ValueTaskType, "type"},
|
||||
{-1, "empty"},
|
||||
{ValueType(999), "unknown"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.want, func(t *testing.T) {
|
||||
got := typeName(tt.typ)
|
||||
if got != tt.want {
|
||||
t.Errorf("typeName(%d) = %q, want %q", tt.typ, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_ResolveEmptyPairBothEmpty(t *testing.T) {
|
||||
a, b := resolveEmptyPair(-1, -1)
|
||||
if a != -1 || b != -1 {
|
||||
t.Errorf("expected both -1, got %d, %d", a, b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_MembershipCompatibleEdgeCases(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
a, b ValueType
|
||||
want bool
|
||||
}{
|
||||
{"same type", ValueString, ValueString, true},
|
||||
{"empty a", -1, ValueString, true},
|
||||
{"empty b", ValueString, -1, true},
|
||||
{"id and ref", ValueID, ValueRef, true},
|
||||
{"ref and id", ValueRef, ValueID, true},
|
||||
{"string and int", ValueString, ValueInt, false},
|
||||
{"status and string", ValueStatus, ValueString, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := membershipCompatible(tt.a, tt.b)
|
||||
if got != tt.want {
|
||||
t.Errorf("membershipCompatible(%d, %d) = %v, want %v", tt.a, tt.b, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_IsRefCompatible(t *testing.T) {
|
||||
tests := []struct {
|
||||
typ ValueType
|
||||
want bool
|
||||
}{
|
||||
{ValueRef, true},
|
||||
{ValueID, true},
|
||||
{ValueString, false},
|
||||
{ValueInt, false},
|
||||
{ValueListRef, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(typeName(tt.typ), func(t *testing.T) {
|
||||
got := isRefCompatible(tt.typ)
|
||||
if got != tt.want {
|
||||
t.Errorf("isRefCompatible(%d) = %v, want %v", tt.typ, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_CheckCompareOpUnknown(t *testing.T) {
|
||||
err := checkCompareOp(ValueInt, "~")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for unknown operator")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "unknown operator") {
|
||||
t.Errorf("expected 'unknown operator', got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_IsOrderableType(t *testing.T) {
|
||||
orderable := []ValueType{ValueInt, ValueDate, ValueTimestamp, ValueDuration, ValueString, ValueStatus, ValueTaskType, ValueID, ValueRef}
|
||||
for _, vt := range orderable {
|
||||
if !isOrderableType(vt) {
|
||||
t.Errorf("expected %s to be orderable", typeName(vt))
|
||||
}
|
||||
}
|
||||
|
||||
notOrderable := []ValueType{ValueBool, ValueListString, ValueListRef, ValueRecurrence}
|
||||
for _, vt := range notOrderable {
|
||||
if isOrderableType(vt) {
|
||||
t.Errorf("expected %s to NOT be orderable", typeName(vt))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_IsStringLike(t *testing.T) {
|
||||
stringLike := []ValueType{ValueString, ValueStatus, ValueTaskType, ValueID, ValueRef}
|
||||
for _, vt := range stringLike {
|
||||
if !isStringLike(vt) {
|
||||
t.Errorf("expected %s to be string-like", typeName(vt))
|
||||
}
|
||||
}
|
||||
|
||||
notStringLike := []ValueType{ValueInt, ValueDate, ValueBool, ValueListString}
|
||||
for _, vt := range notStringLike {
|
||||
if isStringLike(vt) {
|
||||
t.Errorf("expected %s to NOT be string-like", typeName(vt))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_IsEnumType(t *testing.T) {
|
||||
if !isEnumType(ValueStatus) {
|
||||
t.Error("expected ValueStatus to be enum")
|
||||
}
|
||||
if !isEnumType(ValueTaskType) {
|
||||
t.Error("expected ValueTaskType to be enum")
|
||||
}
|
||||
if isEnumType(ValueString) {
|
||||
t.Error("expected ValueString to NOT be enum")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_TypesCompatible(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
a, b ValueType
|
||||
want bool
|
||||
}{
|
||||
{"same type", ValueInt, ValueInt, true},
|
||||
{"empty a", -1, ValueString, true},
|
||||
{"empty b", ValueString, -1, true},
|
||||
{"string-like pair", ValueString, ValueID, true},
|
||||
{"status and string", ValueStatus, ValueString, true},
|
||||
{"int and string", ValueInt, ValueString, false},
|
||||
{"date and int", ValueDate, ValueInt, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := typesCompatible(tt.a, tt.b)
|
||||
if got != tt.want {
|
||||
t.Errorf("typesCompatible(%s, %s) = %v, want %v", typeName(tt.a), typeName(tt.b), got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_InferBinaryExprUnknownOp(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
b := &BinaryExpr{
|
||||
Op: "*",
|
||||
Left: &IntLiteral{Value: 1},
|
||||
Right: &IntLiteral{Value: 2},
|
||||
}
|
||||
_, err := p.inferBinaryExprType(b)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for unknown binary operator")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "unknown binary operator") {
|
||||
t.Errorf("expected 'unknown binary operator', got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_InferExprTypeSubqueryBare(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
sq := &SubQuery{}
|
||||
_, err := p.inferExprType(sq)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for bare subquery")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "subquery is only valid as argument to count") {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type valFakeCondition struct{}
|
||||
|
||||
func (*valFakeCondition) conditionNode() {}
|
||||
|
||||
func TestValidation_ValidateConditionUnknownType(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
err := p.validateCondition(&valFakeCondition{})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for unknown condition type")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "unknown condition type") {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type valFakeExpr struct{}
|
||||
|
||||
func (*valFakeExpr) exprNode() {}
|
||||
|
||||
func TestValidation_InferExprTypeUnknownType(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
_, err := p.inferExprType(&valFakeExpr{})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for unknown expression type")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "unknown expression type") {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_ListElementType(t *testing.T) {
|
||||
tests := []struct {
|
||||
typ ValueType
|
||||
want ValueType
|
||||
}{
|
||||
{ValueListString, ValueString},
|
||||
{ValueListRef, ValueRef},
|
||||
{ValueString, -1},
|
||||
{ValueInt, -1},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := listElementType(tt.typ)
|
||||
if got != tt.want {
|
||||
t.Errorf("listElementType(%s) = %d, want %d", typeName(tt.typ), got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_BeforeTriggerValidation(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
"before with action",
|
||||
`before update update where id = "x" set status="done"`,
|
||||
"before-trigger must not have an action",
|
||||
},
|
||||
{
|
||||
"before without deny",
|
||||
`before update where status = "done"`,
|
||||
"before-trigger must have deny",
|
||||
},
|
||||
{
|
||||
"after with deny",
|
||||
`after update deny "no"`,
|
||||
"after-trigger must not have deny",
|
||||
},
|
||||
{
|
||||
"after without action",
|
||||
`after update where status = "done"`,
|
||||
"after-trigger must have an action",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := p.ParseTrigger(tt.input)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), tt.wantErr) {
|
||||
t.Fatalf("expected error containing %q, got: %v", tt.wantErr, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_InferListTypeRefElements(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
stmt, err := p.ParseStatement(`create title="x" dependsOn=["TIKI-A", "TIKI-B"]`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if stmt.Create == nil {
|
||||
t.Fatal("expected create statement")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_InferListTypeEmptyList(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
stmt, err := p.ParseStatement(`create title="x" tags=[]`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if stmt.Create == nil {
|
||||
t.Fatal("expected create statement")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_DateMinusDuration(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
_, err := p.ParseStatement(`select where due > 2026-03-25 - 1week`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_ListRefPlusIdField(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
_, err := p.ParseStatement(`create title="x" dependsOn=dependsOn + id`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_ListRefMinusIdField(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
_, err := p.ParseStatement(`create title="x" dependsOn=dependsOn - id`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_ListRefMinusListRef(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
_, err := p.ParseStatement(`create title="x" dependsOn=dependsOn - dependsOn`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_IntMinusInt(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
_, err := p.ParseStatement(`create title="x" priority=3 - 1`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_ListStringPlusListString(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
_, err := p.ParseStatement(`create title="x" tags=tags + tags`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_ListStringMinusListString(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
_, err := p.ParseStatement(`create title="x" tags=tags - tags`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_ListRefPlusListRef(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
_, err := p.ParseStatement(`create title="x" dependsOn=dependsOn + dependsOn`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_FuncArgRangeError(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
fc := &FunctionCall{
|
||||
Name: "contains",
|
||||
Args: []Expr{&StringLiteral{Value: "a"}},
|
||||
}
|
||||
_, err := p.inferFuncCallType(fc)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for wrong arg count")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "expects 2 argument(s)") {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_CheckCompareCompatBothEnums(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
_, err := p.ParseStatement(`select where status = type`)
|
||||
if err == nil {
|
||||
t.Fatal("expected error comparing status with type")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "cannot compare") {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation_ListRefAssignFromListString(t *testing.T) {
|
||||
p := newTestParser()
|
||||
|
||||
_, err := p.ParseStatement(`create title="x" dependsOn=tags`)
|
||||
if err == nil {
|
||||
t.Fatal("expected error assigning list<string> to list<ref>")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "cannot assign") {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue