X-Git-Url: https://git.r.bdr.sh/rbdr/mobius/blobdiff_plain/22c599abc18895f73e96095f35b71cf3357d41b4..d9bc63a10d0978d9a5222cf7be74044e55f409b7:/hotline/server_blackbox_test.go diff --git a/hotline/server_blackbox_test.go b/hotline/server_blackbox_test.go index 3718bd2..45d32d4 100644 --- a/hotline/server_blackbox_test.go +++ b/hotline/server_blackbox_test.go @@ -1,321 +1,86 @@ package hotline -// Guest login -// Admin login is -// -//type testCase struct { -// name string // test case description -// account Account // Account struct for a user that will test transaction will execute under -// request *Transaction // transaction that will be sent by the client to the server -// setup func() // Optional test-specific setup required for the scenario -// teardown func() // Optional test-specific teardown for the scenario -// mockHandler map[int]*mockClientHandler -//} -// -//func (tt *testCase) Setup(srv *Server) error { -// if err := srv.NewUser(tt.account.Login, tt.account.Name, NegatedUserString([]byte(tt.account.Password)), *tt.account.Access); err != nil { -// return err -// } -// -// if tt.setup != nil { -// tt.setup() -// } -// -// return nil -//} -// -//func (tt *testCase) Teardown(srv *Server) error { -// if err := srv.DeleteUser(tt.account.Login); err != nil { -// return err -// } -// -// if tt.teardown != nil { -// tt.teardown() -// } -// -// return nil -//} -// -//func NewTestLogger() *zap.SugaredLogger { -// encoderCfg := zap.NewProductionEncoderConfig() -// encoderCfg.TimeKey = "timestamp" -// encoderCfg.EncodeTime = zapcore.ISO8601TimeEncoder -// -// core := zapcore.NewCore( -// zapcore.NewConsoleEncoder(encoderCfg), -// zapcore.Lock(os.Stdout), -// zap.DebugLevel, -// ) -// -// cores := []zapcore.Core{core} -// l := zap.New(zapcore.NewTee(cores...)) -// defer func() { _ = l.Sync() }() -// return l.Sugar() -//} -// -//func StartTestServer() (*Server, context.Context, context.CancelFunc) { -// ctx, cancelRoot := context.WithCancel(context.Background()) -// -// srv, err := NewServer("test/config/", "localhost", 0, NewTestLogger()) -// if err != nil { -// panic(err) -// } -// -// go func() { -// err := srv.ListenAndServe(ctx, cancelRoot) -// if err != nil { -// panic(err) -// } -// }() -// -// return srv, ctx, cancelRoot -//} -// -//func TestHandshake(t *testing.T) { -// srv, _, cancelFunc := StartTestServer() -// defer cancelFunc() -// -// port := srv.APIListener.Addr().(*net.TCPAddr).Port -// -// conn, err := net.Dial("tcp", fmt.Sprintf(":%v", port)) -// if err != nil { -// t.Fatal(err) -// } -// defer conn.Close() -// -// conn.Write([]byte{0x54, 0x52, 0x54, 0x50, 0x00, 0x01, 0x00, 0x00}) -// -// replyBuf := make([]byte, 8) -// _, _ = conn.Read(replyBuf) -// -// want := []byte{84, 82, 84, 80, 0, 0, 0, 0} -// if bytes.Compare(replyBuf, want) != 0 { -// t.Errorf("%q, want %q", replyBuf, want) -// } -// -//} -// -////func TestLogin(t *testing.T) { -//// -//// tests := []struct { -//// name string -//// client *Client -//// }{ -//// { -//// name: "when login is successful", -//// client: NewClient("guest", NewTestLogger()), -//// }, -//// } -//// for _, test := range tests { -//// t.Run(test.name, func(t *testing.T) { -//// -//// }) -//// } -////} -// -//func TestNewUser(t *testing.T) { -// srv, _, _ := StartTestServer() -// -// tests := []testCase{ -// //{ -// // name: "a valid new account", -// // mockHandler: func() mockClientHandler { -// // mh := mockClientHandler{} -// // mh.On("Handle", mock.AnythingOfType("*hotline.Client"), mock.MatchedBy(func(t *Transaction) bool { -// // println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@") -// // spew.Dump(t.Type) -// // spew.Dump(bytes.Equal(t.Type, []byte{0x01, 0x5e})) -// // //if !bytes.Equal(t.GetField(fieldError).Data, []byte("You are not allowed to create new accounts.")) { -// // // return false -// // //} -// // return bytes.Equal(t.Type, []byte{0x01, 0x5e}, -// // ) -// // })).Return( -// // []Transaction{}, nil, -// // ) -// // -// // clientHandlers[tranNewUser] = mh -// // return mh -// // }(), -// // client: func() *Client { -// // c := NewClient("testUser", NewTestLogger()) -// // return c -// // }(), -// // teardown: func() { -// // _ = srv.DeleteUser("testUser") -// // }, -// // account: Account{ -// // Login: "test", -// // Name: "unnamed", -// // Password: "test", -// // Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255}, -// // }, -// // request: NewTransaction( -// // tranNewUser, nil, -// // NewField(fieldUserLogin, []byte(NegatedUserString([]byte("testUser")))), -// // NewField(fieldUserName, []byte("testUserName")), -// // NewField(fieldUserPassword, []byte(NegatedUserString([]byte("testPw")))), -// // NewField(fieldUserAccess, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), -// // ), -// // want: &Transaction{ -// // Fields: []Field{}, -// // }, -// //}, -// //{ -// // name: "a newUser request from a user without the required access", -// // mockHandler: func() *mockClientHandler { -// // mh := mockClientHandler{} -// // mh.On("Handle", mock.AnythingOfType("*hotline.Client"), mock.MatchedBy(func(t *Transaction) bool { -// // if !bytes.Equal(t.GetField(fieldError).Data, []byte("You are not allowed to create new accounts.")) { -// // return false -// // } -// // return bytes.Equal(t.Type, []byte{0x01, 0x5e}) -// // })).Return( -// // []Transaction{}, nil, -// // ) -// // return &mh -// // }(), -// // teardown: func() { -// // _ = srv.DeleteUser("testUser") -// // }, -// // account: Account{ -// // Login: "test", -// // Name: "unnamed", -// // Password: "test", -// // Access: &[]byte{0, 0, 0, 0, 0, 0, 0, 0}, -// // }, -// // request: NewTransaction( -// // tranNewUser, nil, -// // NewField(fieldUserLogin, []byte(NegatedUserString([]byte("testUser")))), -// // NewField(fieldUserName, []byte("testUserName")), -// // NewField(fieldUserPassword, []byte(NegatedUserString([]byte("testPw")))), -// // NewField(fieldUserAccess, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), -// // ), -// //}, -// { -// name: "when user does not have required permission", -// mockHandler: func() map[int]*mockClientHandler { -// mockHandlers := make(map[int]*mockClientHandler) -// -// mh := mockClientHandler{} -// mh.On("Handle", mock.AnythingOfType("*hotline.Client"), mock.MatchedBy(func(t *Transaction) bool { -// return t.equal(Transaction{ -// Type: []byte{0x01, 0x5e}, -// IsReply: 1, -// ErrorCode: []byte{0, 0, 0, 1}, -// Fields: []Field{ -// NewField(fieldError, []byte("You are not allowed to create new accounts.")), -// }, -// }) -// })).Return( -// []Transaction{}, nil, -// ) -// mockHandlers[tranNewUser] = &mh -// -// return mockHandlers -// }(), -// -// teardown: func() { -// _ = srv.DeleteUser("testUser") -// }, -// account: Account{ -// Login: "test", -// Name: "unnamed", -// Password: "test", -// Access: &[]byte{0, 0, 0, 0, 0, 0, 0, 0}, -// }, -// request: NewTransaction( -// tranNewUser, nil, -// NewField(fieldUserLogin, []byte(NegatedUserString([]byte("testUser")))), -// NewField(fieldUserName, []byte("testUserName")), -// NewField(fieldUserPassword, []byte(NegatedUserString([]byte("testPw")))), -// NewField(fieldUserAccess, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), -// ), -// }, -// -// //{ -// // name: "a request to create a user that already exists", -// // setup: func() { -// // -// // }, -// // teardown: func() { -// // _ = srv.DeleteUser("testUser") -// // }, -// // account: Account{ -// // Login: "test", -// // Name: "unnamed", -// // Password: "test", -// // Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255}, -// // }, -// // request: NewTransaction( -// // tranNewUser, nil, -// // NewField(fieldUserLogin, []byte(NegatedUserString([]byte("guest")))), -// // NewField(fieldUserName, []byte("testUserName")), -// // NewField(fieldUserPassword, []byte(NegatedUserString([]byte("testPw")))), -// // NewField(fieldUserAccess, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), -// // ), -// // want: &Transaction{ -// // Fields: []Field{ -// // NewField(fieldError, []byte("Cannot create account guest because there is already an account with that login.")), -// // }, -// // }, -// //}, -// } -// -// for _, test := range tests { -// t.Run(test.name, func(t *testing.T) { -// test.Setup(srv) -// -// // move to Setup? -// c := NewClient(test.account.Name, NewTestLogger()) -// err := c.JoinServer(fmt.Sprintf(":%v", srv.APIPort()), test.account.Login, test.account.Password) -// if err != nil { -// t.Errorf("login failed: %v", err) -// } -// // end move to Setup?? -// -// for key, value := range test.mockHandler { -// c.Handlers[uint16(key)] = value -// } -// -// // send test case request -// _ = c.Send(*test.request) -// -// //time.Sleep(1 * time.Second) -// // === -// -// transactions, _ := readN(c.Connection, 1) -// for _, t := range transactions { -// _ = c.HandleTransaction(&t) -// } -// -// // === -// -// for _, handler := range test.mockHandler { -// handler.AssertExpectations(t) -// } -// -// test.Teardown(srv) -// }) -// } -//} -// -//// equal is a utility function used only in tests that determines if transactions are equal enough -//func (t Transaction) equal(otherT Transaction) bool { -// t.ID = []byte{0, 0, 0, 0} -// otherT.ID = []byte{0, 0, 0, 0} -// -// t.TotalSize = []byte{0, 0, 0, 0} -// otherT.TotalSize = []byte{0, 0, 0, 0} -// -// t.DataSize = []byte{0, 0, 0, 0} -// otherT.DataSize = []byte{0, 0, 0, 0} -// -// t.ParamCount = []byte{0, 0} -// otherT.ParamCount = []byte{0, 0} -// -// //spew.Dump(t) -// //spew.Dump(otherT) -// -// return reflect.DeepEqual(t, otherT) -//} +import ( + "cmp" + "encoding/binary" + "encoding/hex" + "github.com/stretchr/testify/assert" + "log/slog" + "os" + "slices" + "testing" +) + +func NewTestLogger() *slog.Logger { + return slog.New(slog.NewTextHandler(os.Stdout, nil)) +} + +// assertTransferBytesEqual takes a string with a hexdump in the same format that `hexdump -C` produces and compares with +// a hexdump for the bytes in got, after stripping the create/modify timestamps. +// I don't love this, but as git does not preserve file create/modify timestamps, we either need to fully mock the +// filesystem interactions or work around in this way. +// TODO: figure out a better solution +func assertTransferBytesEqual(t *testing.T, wantHexDump string, got []byte) bool { + if wantHexDump == "" { + return true + } + + clean := slices.Concat( + got[:92], + make([]byte, 16), + got[108:], + ) + return assert.Equal(t, wantHexDump, hex.Dump(clean)) +} + +var tranSortFunc = func(a, b Transaction) int { + return cmp.Compare( + binary.BigEndian.Uint16(a.clientID[:]), + binary.BigEndian.Uint16(b.clientID[:]), + ) +} + +// tranAssertEqual compares equality of transactions slices after stripping out the random transaction Type +func tranAssertEqual(t *testing.T, tran1, tran2 []Transaction) bool { + var newT1 []Transaction + var newT2 []Transaction + + for _, trans := range tran1 { + trans.ID = [4]byte{0, 0, 0, 0} + var fs []Field + for _, field := range trans.Fields { + if field.Type == FieldRefNum { // FieldRefNum + continue + } + if field.Type == FieldChatID { // FieldChatID + continue + } + + fs = append(fs, field) + } + trans.Fields = fs + newT1 = append(newT1, trans) + } + + for _, trans := range tran2 { + trans.ID = [4]byte{0, 0, 0, 0} + var fs []Field + for _, field := range trans.Fields { + if field.Type == FieldRefNum { // FieldRefNum + continue + } + if field.Type == FieldChatID { // FieldChatID + continue + } + + fs = append(fs, field) + } + trans.Fields = fs + newT2 = append(newT2, trans) + } + + slices.SortFunc(newT1, tranSortFunc) + slices.SortFunc(newT2, tranSortFunc) + + return assert.Equal(t, newT1, newT2) +}