From dc7ecff40bef44b67e9355b546d0ccff980b1a56 Mon Sep 17 00:00:00 2001 From: ysugimoto Date: Sun, 2 Feb 2025 04:54:44 +0900 Subject: [PATCH] implement assert.not_state assertion function --- docs/testing.md | 17 ++++++ tester/function/assert_not_state.go | 51 ++++++++++++++++ tester/function/assert_not_state_test.go | 58 +++++++++++++++++++ .../{testing_functions.go => functions.go} | 20 +++++++ 4 files changed, 146 insertions(+) create mode 100644 tester/function/assert_not_state.go create mode 100644 tester/function/assert_not_state_test.go rename tester/function/{testing_functions.go => functions.go} (96%) diff --git a/docs/testing.md b/docs/testing.md index a6212183..09e1c2ec 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -229,6 +229,7 @@ We describe them following table and examples: | assert.not_subroutine_called | FUNCTION | Assert subroutine has not called in testing subroutine | | assert.restart | FUNCTION | Assert restart statement has called | | assert.state | FUNCTION | Assert after state is expected one | +| assert.not_state | FUNCTION | Assert after state is not expected one | | assert.error | FUNCTION | Assert error status code (and response) if error statement has called | | assert.not_error | FUNCTION | Assert runtime state will not move to error status | @@ -873,6 +874,22 @@ sub test_vcl { ---- +### assert.not_state(ID state [, STRING message]) + +Assert current state is not expected one. + +```vcl +sub test_vcl { + // vcl_recv will move state to lookup to lookup cache + testing.call_subroutine("vcl_recv"); + + // Assert state does not move to lookup + assert.not_state(lookup); +} +``` + +---- + ### assert.error(INTEGER status [, STRING response, STRING message]) Assert error status code (and response) if error statement has called. diff --git a/tester/function/assert_not_state.go b/tester/function/assert_not_state.go new file mode 100644 index 00000000..eeb612eb --- /dev/null +++ b/tester/function/assert_not_state.go @@ -0,0 +1,51 @@ +package function + +import ( + "fmt" + + "github.com/ysugimoto/falco/interpreter" + "github.com/ysugimoto/falco/interpreter/context" + "github.com/ysugimoto/falco/interpreter/function/errors" + "github.com/ysugimoto/falco/interpreter/value" +) + +const Assert_not_state_Name = "assert.not_state" + +var Assert_not_state_ArgumentTypes = []value.Type{value.IdentType} + +func Assert_not_state_Validate(args []value.Value) error { + if len(args) > 2 { + return errors.ArgumentNotInRange(Assert_not_state_Name, 1, 2, args) + } + + for i := range Assert_not_state_ArgumentTypes { + if args[i].Type() != Assert_not_state_ArgumentTypes[i] { + return errors.TypeMismatch(Assert_not_state_Name, i+1, Assert_not_state_ArgumentTypes[i], args[i].Type()) + } + } + + return nil +} + +func Assert_not_state( + ctx *context.Context, + i *interpreter.Interpreter, + args ...value.Value, +) (value.Value, error) { + + if err := Assert_not_state_Validate(args); err != nil { + return nil, errors.NewTestingError("%s", err.Error()) + } + + state := value.Unwrap[*value.Ident](args[0]) + expect := interpreter.StateFromString(state.Value) + + var message string + if len(args) == 2 { + message = value.Unwrap[*value.String](args[0]).Value + } else { + message = fmt.Sprintf("state should not be %s", expect) + } + + return assert_not(state, expect.String(), i.TestingState.String(), message) +} diff --git a/tester/function/assert_not_state_test.go b/tester/function/assert_not_state_test.go new file mode 100644 index 00000000..4547fb5e --- /dev/null +++ b/tester/function/assert_not_state_test.go @@ -0,0 +1,58 @@ +package function + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/ysugimoto/falco/interpreter" + "github.com/ysugimoto/falco/interpreter/context" + "github.com/ysugimoto/falco/interpreter/function/errors" + "github.com/ysugimoto/falco/interpreter/value" +) + +func Test_Assert_not_state(t *testing.T) { + + tests := []struct { + args []value.Value + ip *interpreter.Interpreter + err error + expect *value.Boolean + }{ + { + args: []value.Value{ + &value.Ident{Value: "error"}, + }, + ip: &interpreter.Interpreter{ + TestingState: interpreter.ERROR, + }, + expect: &value.Boolean{Value: false}, + err: &errors.AssertionError{}, + }, + { + args: []value.Value{ + &value.Ident{Value: "error"}, + }, + ip: &interpreter.Interpreter{ + TestingState: interpreter.LOOKUP, + }, + expect: &value.Boolean{Value: true}, + }, + } + + for i := range tests { + _, err := Assert_not_state( + &context.Context{}, + tests[i].ip, + tests[i].args..., + ) + if diff := cmp.Diff( + tests[i].err, + err, + cmpopts.IgnoreFields(errors.AssertionError{}, "Message", "Actual"), + cmpopts.IgnoreFields(errors.TestingError{}, "Message"), + ); diff != "" { + t.Errorf("Assert_not_state()[%d] error: diff=%s", i, diff) + } + } +} diff --git a/tester/function/testing_functions.go b/tester/function/functions.go similarity index 96% rename from tester/function/testing_functions.go rename to tester/function/functions.go index 9db6a1df..11b8fdd4 100644 --- a/tester/function/testing_functions.go +++ b/tester/function/functions.go @@ -255,6 +255,26 @@ func assertionFunctions(i *interpreter.Interpreter, c Counter) Functions { return false }, }, + "assert.not_state": { + Scope: allScope, + Call: func(ctx *context.Context, args ...value.Value) (value.Value, error) { + unwrapped, err := unwrapIdentArguments(i, args) + if err != nil { + return value.Null, errors.WithStack(err) + } + v, err := Assert_not_state(ctx, i, unwrapped...) + if err != nil { + c.Fail() + } else { + c.Pass() + } + return v, err + }, + CanStatementCall: true, + IsIdentArgument: func(i int) bool { + return false + }, + }, "assert.error": { Scope: allScope, Call: func(ctx *context.Context, args ...value.Value) (value.Value, error) {