diff --git a/internal/tests/integration/commands/get_test.go b/internal/tests/integration/commands/get_test.go new file mode 100644 index 0000000..02eb137 --- /dev/null +++ b/internal/tests/integration/commands/get_test.go @@ -0,0 +1,56 @@ +package commands + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestGet(t *testing.T) { + exec, err := NewHTTPCommandExecutor() + if err != nil { + t.Fatal(err) + } + + defer exec.FlushDB() + + testCases := []TestCase{ + { + Name: "Get with expiration", + Commands: []HTTPCommand{ + {Command: "SET", Body: []string{"k", "v", "EX", "4"}}, + {Command: "GET", Body: []string{"k"}}, + {Command: "GET", Body: []string{"k"}}, + }, + Result: []TestCaseResult{ + {Expected: "OK"}, + {Expected: "v"}, + {Expected: "(nil)"}, + }, + Delays: []time.Duration{0, 0, 5 * time.Second}, + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + for i, cmd := range tc.Commands { + if tc.Delays[i] > 0 { + time.Sleep(tc.Delays[i]) + } + response, err := exec.FireCommand(cmd) + if err != nil { + t.Logf("error in executing command: %s - %v", cmd.Command, err) + } + + result := tc.Result[i] + if result.ErrorExpected { + assert.NotNil(t, err) + assert.Equal(t, result.Expected, err.Error()) + } else { + assert.Equal(t, result.Expected, response) + } + } + }) + } +} diff --git a/internal/tests/integration/commands/incr_test.go b/internal/tests/integration/commands/incr_test.go new file mode 100644 index 0000000..e7df5b6 --- /dev/null +++ b/internal/tests/integration/commands/incr_test.go @@ -0,0 +1,143 @@ +package commands + +import ( + "math" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestIncr(t *testing.T) { + exec, err := NewHTTPCommandExecutor() + if err != nil { + t.Fatal(err) + } + + defer exec.FlushDB() + + testCases := []TestCase{ + { + Name: "Increment multiple keys", + Commands: []HTTPCommand{ + {Command: "SET", Body: []string{"key1", "0"}}, + {Command: "INCR", Body: []string{"key1"}}, + {Command: "INCR", Body: []string{"key1"}}, + {Command: "INCR", Body: []string{"key2"}}, + {Command: "GET", Body: []string{"key1"}}, + {Command: "GET", Body: []string{"key2"}}, + }, + Result: []TestCaseResult{ + {Expected: "OK"}, + {Expected: "1"}, + {Expected: "2"}, + {Expected: "1"}, + {Expected: "2"}, + {Expected: "1"}, + }, + }, + { + Name: "Increment from min int64", + Commands: []HTTPCommand{ + {Command: "SET", Body: []string{"min_int", strconv.Itoa(math.MinInt64)}}, + {Command: "INCR", Body: []string{"min_int"}}, + {Command: "INCR", Body: []string{"min_int"}}, + }, + Result: []TestCaseResult{ + {Expected: "OK"}, + {Expected: strconv.FormatInt(math.MinInt64+1, 10)}, + {Expected: strconv.FormatInt(math.MinInt64+2, 10)}, + }, + }, + { + Name: "Increment non-existent key", + Commands: []HTTPCommand{ + {Command: "INCR", Body: []string{"non_existent"}}, + {Command: "GET", Body: []string{"non_existent"}}, + {Command: "INCR", Body: []string{"non_existent"}}, + }, + Result: []TestCaseResult{ + {Expected: "1"}, + {Expected: "1"}, + {Expected: "2"}, + }, + }, + { + Name: "Increment string representing integers", + Commands: []HTTPCommand{ + {Command: "SET", Body: []string{"str_int1", "42"}}, + {Command: "INCR", Body: []string{"str_int1"}}, + {Command: "SET", Body: []string{"str_int2", "-10"}}, + {Command: "INCR", Body: []string{"str_int2"}}, + {Command: "SET", Body: []string{"str_int3", "0"}}, + {Command: "INCR", Body: []string{"str_int3"}}, + }, + Result: []TestCaseResult{ + {Expected: "OK"}, + {Expected: "43"}, + {Expected: "OK"}, + {Expected: "-9"}, + {Expected: "OK"}, + {Expected: "1"}, + }, + }, + { + Name: "Increment with expiry", + Commands: []HTTPCommand{ + {Command: "SET", Body: []string{"expiry_key", "0", "EX", "1"}}, + {Command: "INCR", Body: []string{"expiry_key"}}, + {Command: "INCR", Body: []string{"expiry_key"}}, + {Command: "GET", Body: []string{"expiry_key"}}, + }, + Result: []TestCaseResult{ + {Expected: "OK"}, + {Expected: "1"}, + {Expected: "2"}, + {Expected: "(nil)"}, + }, + Delays: []time.Duration{0, 0, 0, 2 * time.Second}, + }, + { + Name: "Increment non-integer values", + Commands: []HTTPCommand{ + {Command: "SET", Body: []string{"float_key", "3.14"}}, + {Command: "INCR", Body: []string{"float_key"}}, + {Command: "SET", Body: []string{"string_key", "hello"}}, + {Command: "INCR", Body: []string{"string_key"}}, + {Command: "SET", Body: []string{"bool_key", "true"}}, + {Command: "INCR", Body: []string{"bool_key"}}, + }, + Result: []TestCaseResult{ + {Expected: "OK"}, + {ErrorExpected: true, Expected: "(error) ERR WRONGTYPE Operation against a key holding the wrong kind of value"}, + {Expected: "OK"}, + {ErrorExpected: true, Expected: "(error) ERR WRONGTYPE Operation against a key holding the wrong kind of value"}, + {Expected: "OK"}, + {ErrorExpected: true, Expected: "(error) ERR WRONGTYPE Operation against a key holding the wrong kind of value"}, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + for i, cmd := range tc.Commands { + if tc.Delays != nil && tc.Delays[i] > 0 { + time.Sleep(tc.Delays[i]) + } + response, err := exec.FireCommand(cmd) + if err != nil { + t.Logf("error in executing command: %s - %v", cmd.Command, err) + } + + result := tc.Result[i] + if result.ErrorExpected { + assert.NotNil(t, err) + assert.Equal(t, result.Expected, err.Error()) + } else { + assert.Equal(t, result.Expected, response) + } + } + }) + } +} diff --git a/internal/tests/integration/commands/set_test.go b/internal/tests/integration/commands/set_test.go new file mode 100644 index 0000000..e6b8fa2 --- /dev/null +++ b/internal/tests/integration/commands/set_test.go @@ -0,0 +1,177 @@ +package commands + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSet(t *testing.T) { + exec, err := NewHTTPCommandExecutor() + if err != nil { + t.Fatal(err) + } + + defer exec.FlushDB() + + testCases := []TestCase{ + { + Name: "Set and Get Simple Value", + Commands: []HTTPCommand{ + {Command: "SET", Body: []string{"k", "v"}}, + {Command: "GET", Body: []string{"k"}}, + }, + Result: []TestCaseResult{ + {Expected: "OK"}, + {Expected: "v"}, + }, + }, + { + Name: "Set and Get Integer Value", + Commands: []HTTPCommand{ + {Command: "SET", Body: []string{"k", "123456789"}}, + {Command: "GET", Body: []string{"k"}}, + }, + Result: []TestCaseResult{ + {Expected: "OK"}, + {Expected: "123456789"}, // This is Redis' scientific notation for large numbers + }, + }, + { + Name: "Overwrite Existing Key", + Commands: []HTTPCommand{ + {Command: "SET", Body: []string{"k", "v1"}}, + {Command: "SET", Body: []string{"k", "5"}}, + {Command: "GET", Body: []string{"k"}}, + }, + Result: []TestCaseResult{ + {Expected: "OK"}, + {Expected: "OK"}, + {Expected: "5"}, // As the value 5 is stored as a string + }, + }, + { + Name: "Set with EX and PX option", + Commands: []HTTPCommand{ + {Command: "SET", Body: []string{"k", "v", "EX", "2", "PX", "2000"}}, + }, + Result: []TestCaseResult{ + {ErrorExpected: true, Expected: "(error) ERR syntax error"}, + }, + }, + { + Name: "XX on non-existing key", + Commands: []HTTPCommand{ + {Command: "DEL", Body: []string{"a"}}, + {Command: "SET", Body: []string{"a", "v", "XX"}}, + {Command: "GET", Body: []string{"a"}}, + }, + Result: []TestCaseResult{ + {Expected: "0"}, // DEL returns number of deleted keys + {Expected: "(nil)"}, + {Expected: "(nil)"}, + }, + }, + { + Name: "NX on non-existing key", + Commands: []HTTPCommand{ + {Command: "DEL", Body: []string{"c"}}, + {Command: "SET", Body: []string{"c", "v", "NX"}}, + {Command: "GET", Body: []string{"c"}}, + }, + Result: []TestCaseResult{ + {Expected: "0"}, // DEL returns number of deleted keys + {Expected: "OK"}, + {Expected: "v"}, + }, + }, + { + Name: "NX on existing key", + Commands: []HTTPCommand{ + {Command: "DEL", Body: []string{"b"}}, + {Command: "SET", Body: []string{"b", "v", "NX"}}, + {Command: "GET", Body: []string{"b"}}, + {Command: "SET", Body: []string{"b", "v", "NX"}}, + }, + Result: []TestCaseResult{ + {Expected: "0"}, // DEL returns number of deleted keys + {Expected: "OK"}, + {Expected: "v"}, + {Expected: "(nil)"}, // NX fails because the key already exists + }, + }, + { + Name: "PXAT option with invalid unix time ms", + Commands: []HTTPCommand{ + {Command: "SET", Body: []string{"k2", "v2", "PXAT", "123123"}}, + {Command: "GET", Body: []string{"k2"}}, + }, + Result: []TestCaseResult{ + {Expected: "OK"}, + {Expected: "(nil)"}, // Invalid time causes key not to be set properly + }, + }, + { + Name: "XX on existing key", + Commands: []HTTPCommand{ + {Command: "SET", Body: []string{"k", "v1"}}, + {Command: "SET", Body: []string{"k", "v2", "XX"}}, + {Command: "GET", Body: []string{"k"}}, + }, + Result: []TestCaseResult{ + {Expected: "OK"}, + {Expected: "OK"}, + {Expected: "v2"}, + }, + }, + { + Name: "Multiple XX operations", + Commands: []HTTPCommand{ + {Command: "SET", Body: []string{"k", "v1"}}, + {Command: "SET", Body: []string{"k", "v2", "XX"}}, + {Command: "SET", Body: []string{"k", "v3", "XX"}}, + {Command: "GET", Body: []string{"k"}}, + }, + Result: []TestCaseResult{ + {Expected: "OK"}, + {Expected: "OK"}, + {Expected: "OK"}, + {Expected: "v3"}, + }, + }, + { + Name: "EX option", + Commands: []HTTPCommand{ + {Command: "SET", Body: []string{"k", "v", "EX", "1"}}, + {Command: "GET", Body: []string{"k"}}, + {Command: "SLEEP", Body: []string{"2"}}, + {Command: "GET", Body: []string{"k"}}, + }, + Result: []TestCaseResult{ + {Expected: "OK"}, + {Expected: "v"}, + {Expected: "OK"}, + {Expected: "(nil)"}, // After expiration + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + for i, cmd := range tc.Commands { + response, err := exec.FireCommand(cmd) + if err != nil { + t.Logf("error in executing command: %s - %v", cmd.Command, err) + } + + result := tc.Result[i] + if result.ErrorExpected { + assert.NotNil(t, err) + assert.Equal(t, result.Expected, err.Error()) + } else { + assert.Equal(t, result.Expected, response) + } + } + }) + } +} diff --git a/internal/tests/integration/commands/setup.go b/internal/tests/integration/commands/setup.go index 9945b21..f6a5a43 100644 --- a/internal/tests/integration/commands/setup.go +++ b/internal/tests/integration/commands/setup.go @@ -11,6 +11,7 @@ import ( "server/config" "server/internal/db" "server/internal/server" + "time" ) type HTTPCommand struct { @@ -35,6 +36,7 @@ type TestCase struct { Name string Commands []HTTPCommand Result []TestCaseResult + Delays []time.Duration } func NewHTTPCommandExecutor() (*HTTPCommandExecutor, error) {