From 7e8842c630e05076b8cf50a9ab9e64e7c1e1275b Mon Sep 17 00:00:00 2001 From: mitch Date: Tue, 1 Mar 2022 11:55:49 -0500 Subject: [PATCH] initial commit --- .idea/.gitignore | 8 +++ .idea/loginToTemplate.iml | 9 +++ .idea/modules.xml | 8 +++ file_system_store.go | 73 +++++++++++++++++++ file_system_store_test.go | 139 +++++++++++++++++++++++++++++++++++++ go.mod | 3 + in_memory_user_store.go | 17 +++++ main.go | 16 +++++ server.go | 57 +++++++++++++++ server_integration_test.go | 11 +++ server_test.go | 48 +++++++++++++ tape.go | 14 ++++ tape_test.go | 25 +++++++ testing.go | 81 +++++++++++++++++++++ user.go | 20 ++++++ user_list.go | 31 +++++++++ user_test.go | 51 ++++++++++++++ 17 files changed, 611 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/loginToTemplate.iml create mode 100644 .idea/modules.xml create mode 100644 file_system_store.go create mode 100644 file_system_store_test.go create mode 100644 go.mod create mode 100644 in_memory_user_store.go create mode 100644 main.go create mode 100644 server.go create mode 100644 server_integration_test.go create mode 100644 server_test.go create mode 100644 tape.go create mode 100644 tape_test.go create mode 100644 testing.go create mode 100644 user.go create mode 100644 user_list.go create mode 100644 user_test.go diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/loginToTemplate.iml b/.idea/loginToTemplate.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/loginToTemplate.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..86e0bc5 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/file_system_store.go b/file_system_store.go new file mode 100644 index 0000000..2a545b4 --- /dev/null +++ b/file_system_store.go @@ -0,0 +1,73 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "reflect" +) + +type FileSystemUserStore struct { + database *json.Encoder + userList UserList +} + +func NewFileSystemUserStore(file *os.File) (*FileSystemUserStore, error) { + err := initialiseUserDBFile(file) + if err != nil { + fmt.Errorf("problem initializing user db file, %v", err) + } + + userList, err := NewUserList(file) + + if err != nil { + return nil, fmt.Errorf("problem loading user list from file %s, %v", file.Name(), err) + } + + return &FileSystemUserStore{ + database: json.NewEncoder(&Tape{file}), + userList: userList, + }, nil +} + +func initialiseUserDBFile(file *os.File) error { + file.Seek(0, 0) + + info, err := file.Stat() + + if err != nil { + return fmt.Errorf("problem getting file info from file %s, %v", file.Name(), err) + } + + if info.Size() == 0 { + file.Write([]byte("[]")) + file.Seek(0, 0) + } + + return nil +} + +// todo add interface + +func (f *FileSystemUserStore) AddUserToUserList(user User) { + f.userList = append(f.userList, user) +} + +func (f *FileSystemUserStore) DeleteUserFromUserList(user User) { + var userList []User + for _, u := range f.userList { + if !reflect.DeepEqual(u, user) { + userList = append(userList, u) + } + } + f.userList = userList +} + +func (f *FileSystemUserStore) GetUsers() UserList { + return f.userList +} + +func (f *FileSystemUserStore) UpdateUserFromUserList(toUpdate, newState User) { + user := f.userList.Find(toUpdate.Username) + *user = newState +} diff --git a/file_system_store_test.go b/file_system_store_test.go new file mode 100644 index 0000000..78b62b7 --- /dev/null +++ b/file_system_store_test.go @@ -0,0 +1,139 @@ +package main + +import ( + "io/ioutil" + "os" + "testing" +) + +func createTempFile(t testing.TB, initialDB string) (*os.File, func()) { + t.Helper() + + tempfile, err := ioutil.TempFile("", "db") + + if err != nil { + t.Fatalf("could not create temp file %v", err) + } + + tempfile.Write([]byte(initialDB)) + + removeFile := func() { + tempfile.Close() + os.Remove(tempfile.Name()) + } + + return tempfile, removeFile +} + +func TestFileSystemStore(t *testing.T) { + t.Run("get username", func(t *testing.T) { + database, cleanDatabase := createTempFile(t, `[ + {"Username": "testUser", "Key": "12345", "IpAddress": "123.123.123.123.50120", "IsAdmin": true}, + {"Username": "jimmy", "Key": "67890", "IpAddress": "12.12.12.12:50121", "IsAdmin": false}]`) + defer cleanDatabase() + + store, err := NewFileSystemUserStore(database) + + //userList := store.userList + //user := userList.Find("testUser") + //user.UpdateAdminStatus(true) + + AssertNoError(t, err) + + got := store.GetUsers() + + want := []User{ + {Username: "testUser", Key: "12345", IpAddress: "123.123.123.123.50120", IsAdmin: true}, + {Username: "jimmy", Key: "67890", IpAddress: "12.12.12.12:50121", IsAdmin: false}, + } + + AssertUsers(t, got, want) + + //read again + got = store.GetUsers() + AssertUsers(t, got, want) + }) +} + +func TestCreateUser(t *testing.T) { + t.Run("get username", func(t *testing.T) { + database, cleanDatabase := createTempFile(t, `[ + {"Username": "testUser", "Key": "12345", "IpAddress": "123.123.123.123.50120", "IsAdmin": true}, + {"Username": "jimmy", "Key": "67890", "IpAddress": "12.12.12.12:50121", "IsAdmin": false}]`) + defer cleanDatabase() + + store, err := NewFileSystemUserStore(database) + + AssertNoError(t, err) + + want := User{ + Username: "newUser", + Key: "12345", + IpAddress: "1.1.1.1:1111", + IsAdmin: false, + } + + store.AddUserToUserList(want) + + got := store.GetUsers() + AssertUserExists(t, got, want) + }) +} + +func TestDeleteUser(t *testing.T) { + t.Run("get username", func(t *testing.T) { + database, cleanDatabase := createTempFile(t, `[ + {"Username": "testUser", "Key": "12345", "IpAddress": "123.123.123.123.50120", "IsAdmin": true}, + {"Username": "jimmy", "Key": "67890", "IpAddress": "12.12.12.12:50121", "IsAdmin": false}]`) + defer cleanDatabase() + + store, err := NewFileSystemUserStore(database) + + AssertNoError(t, err) + + want := User{ + Username: "testUser", + Key: "12345", + IpAddress: "123.123.123.123:50120", + IsAdmin: false, + } + + want = *store.userList.Find("testUser") + store.DeleteUserFromUserList(want) + + got := store.GetUsers() + AssertUserNotExists(t, got, want) + + want = *store.userList.Find("jimmy") + AssertUserExists(t, got, want) + }) +} + +//todo move this to user_test.go and user.go +func TestUpdateUserStore(t *testing.T) { + t.Run("get username", func(t *testing.T) { + database, cleanDatabase := createTempFile(t, `[ + {"Username": "testUser", "Key": "12345", "IpAddress": "123.123.123.123.50120", "IsAdmin": true}, + {"Username": "jimmy", "Key": "67890", "IpAddress": "12.12.12.12:50121", "IsAdmin": false}]`) + defer cleanDatabase() + + store, err := NewFileSystemUserStore(database) + + AssertNoError(t, err) + + userToUpdate := *store.userList.Find("testUser") + + want := User{ + Username: "testUser2", + Key: "123456", + IpAddress: "124.124.124.124:50120", + IsAdmin: false, + } + + want = *store.userList.Find("testUser") + store.UpdateUserFromUserList(userToUpdate, want) + + got := store.GetUsers() + AssertUserExists(t, got, want) + }) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6011355 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module loginToTemplate + +go 1.17 diff --git a/in_memory_user_store.go b/in_memory_user_store.go new file mode 100644 index 0000000..de9f1e5 --- /dev/null +++ b/in_memory_user_store.go @@ -0,0 +1,17 @@ +package main + +func NewInMemoryUserStore() *InMemoryUserStore { + return &InMemoryUserStore{map[string]string{}} +} + +type InMemoryUserStore struct { + store map[string]string +} + +func (i InMemoryUserStore) GetUserName(name string) string { + return name +} + +func (i InMemoryUserStore) ShowIP(name string) string { + return i.store[name] +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..69d4d58 --- /dev/null +++ b/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "log" + "net/http" +) + +const hostAndPort = ":5000" + +func main() { + server := NewLoginServer(NewInMemoryUserStore()) + + if err := http.ListenAndServe(hostAndPort, server); err != nil { + log.Fatalf("could not listen on port %v", err) + } +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..5bb2b9e --- /dev/null +++ b/server.go @@ -0,0 +1,57 @@ +package main + +import ( + "net/http" +) + +type UserStore interface { + GetUserName(name string) string + ShowIP(name string) string +} + +type ServerHandler struct { + store UserStore + http.Handler + userIpMap map[string]string +} + +func NewLoginServer(store UserStore) *ServerHandler { + s := &ServerHandler{ + store, + http.NewServeMux(), + nil, + } + router := http.NewServeMux() + router.Handle("/", http.HandlerFunc(s.homeHandler)) + s.Handler = router + return s +} + +func (s *ServerHandler) homeHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodPost: + s.updateIpHandler(w, r) + case http.MethodGet: + s.sendHomeHandler(w, r) + } +} + +func (s *ServerHandler) sendHomeHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(r.RemoteAddr)) +} + +func (s *ServerHandler) updateIpHandler(w http.ResponseWriter, r *http.Request) { + //userName := strings.TrimPrefix(r.URL.Path, "/") + //if ! s.store.GetUserName(userName) + w.WriteHeader(http.StatusAccepted) + w.Write([]byte(r.RemoteAddr)) +} + +func (s *ServerHandler) GetUserName(name string) string { + return name +} + +func (s *ServerHandler) ShowIP(name string) string { + return s.userIpMap[name] +} diff --git a/server_integration_test.go b/server_integration_test.go new file mode 100644 index 0000000..6e8fc93 --- /dev/null +++ b/server_integration_test.go @@ -0,0 +1,11 @@ +package main + +//func TestPortalLoads(t *testing.T) { +// t.Run("Test Portal Loads", func(t *testing.T) { +// server := NewServer() +// +// servr.ServeHTTP(httptest.NewRecorder(), request) +// +// t.Run("") +// }) +//} diff --git a/server_test.go b/server_test.go new file mode 100644 index 0000000..8dbd3f1 --- /dev/null +++ b/server_test.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" +) + +const jsonContentType = "application/json" + +func TestPortal(t *testing.T) { + store := StubUserStore{ + map[string]string{ + "testUser": "123.123.123.123:50120", + "jimmy": "12.12.12.12:50121", + }, + } + server := NewLoginServer(&store) + + t.Run("Logs client ip", func(t *testing.T) { + for _, want := range store.userIpMap { + t.Run(fmt.Sprintf("got ip from "+want), func(t *testing.T) { + request := NewGetHomeRequest() + request.RemoteAddr = want + response := httptest.NewRecorder() + server.ServeHTTP(response, request) + got := response.Body.String() + + AssertStatus(t, response.Code, http.StatusOK) + //todo we should update on post not get + AssertResponseBody(t, got, want) + //t.Fatalf("This test is bad, we should store on post not get") + }) + } + }) + + t.Run("Update client IP", func(t *testing.T) { + request := NewPutHomeRequest("testUser") + want := "5.5.5.5:10121" + request.RemoteAddr = want + response := httptest.NewRecorder() + server.ServeHTTP(response, request) + //todo make this fail + response.Body.String() + AssertStatus(t, response.Code, http.StatusAccepted) + }) +} diff --git a/tape.go b/tape.go new file mode 100644 index 0000000..b36990b --- /dev/null +++ b/tape.go @@ -0,0 +1,14 @@ +package main + +import "os" + +// Tape represents an os.File that will re-write from the start on every Write call. +type Tape struct { + File *os.File +} + +func (t *Tape) Write(p []byte) (n int, err error) { + t.File.Truncate(0) + t.File.Seek(0, 0) + return t.File.Write(p) +} diff --git a/tape_test.go b/tape_test.go new file mode 100644 index 0000000..36d0d9a --- /dev/null +++ b/tape_test.go @@ -0,0 +1,25 @@ +package main + +import ( + "io/ioutil" + "testing" +) + +func Test_Tape_Write(t *testing.T) { + file, clean := createTempFile(t, "12345") + defer clean() + + tape := Tape{File: file} + + tape.Write([]byte("foo")) + + file.Seek(0, 0) + contents, _ := ioutil.ReadAll(file) + + got := string(contents) + want := "foo" + + if got != want { + t.Errorf("got %v want %v", got, want) + } +} diff --git a/testing.go b/testing.go new file mode 100644 index 0000000..68f5582 --- /dev/null +++ b/testing.go @@ -0,0 +1,81 @@ +package main + +import ( + "fmt" + "net/http" + "reflect" + "testing" +) + +type StubUserStore struct { + userIpMap map[string]string +} + +func (s *StubUserStore) GetUserName(name string) string { + return name +} + +func (s *StubUserStore) ShowIP(name string) string { + return s.userIpMap[name] +} + +func AssertNoError(t testing.TB, err error) { + t.Helper() + if err != nil { + t.Fatalf("didn't expect an error, but got one, %v", err) + } +} + +func NewGetHomeRequest() *http.Request { + req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/"), nil) + return req +} + +func NewPutHomeRequest(userName string) *http.Request { + req, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("/"+userName), nil) + return req +} + +func userInUserList(ul UserList, user User) bool { + for _, u := range ul { + if reflect.DeepEqual(u, user) { + return true + } + } + return false +} + +func AssertResponseBody(t testing.TB, got, want string) { + t.Helper() + if got != want { + t.Errorf("response body is wrong, got %q want %q", got, want) + } +} + +func AssertStatus(t testing.TB, got, want int) { + t.Helper() + if got != want { + t.Errorf("did not get correct status, expected %d got %d", got, want) + } +} + +func AssertUsers(t testing.TB, got, want []User) { + t.Helper() + if !reflect.DeepEqual(got, want) { + t.Errorf("got %v want %v", got, want) + } +} + +func AssertUserExists(t testing.TB, got []User, want User) { + t.Helper() + if !userInUserList(got, want) { + t.Errorf("got %v want %v", got, want) + } +} + +func AssertUserNotExists(t testing.TB, got []User, want User) { + t.Helper() + if userInUserList(got, want) { + t.Errorf("Expected %v to be deleted but it was not. List is %v", want, got) + } +} diff --git a/user.go b/user.go new file mode 100644 index 0000000..f966dfd --- /dev/null +++ b/user.go @@ -0,0 +1,20 @@ +package main + +type User struct { + Username string + Key string + IpAddress string + IsAdmin bool +} + +func (u *User) UpdateUserKey(key string) { + u.Key = key +} + +func (u *User) UpdateUserIp(ipAddress string) { + u.IpAddress = ipAddress +} + +func (u *User) UpdateAdminStatus(isAdmin bool) { + u.IsAdmin = isAdmin +} diff --git a/user_list.go b/user_list.go new file mode 100644 index 0000000..53a6974 --- /dev/null +++ b/user_list.go @@ -0,0 +1,31 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" +) + +// UserList stores a collection of users. +type UserList []User + +// Find tries to return username from UserList +func (ul UserList) Find(username string) *User { + for i, u := range ul { + if u.Username == username { + return &ul[i] + } + } + return nil +} + +// NewUserList creates a UserList from JSON +func NewUserList(rdr io.Reader) (UserList, error) { + var userList []User + err := json.NewDecoder(rdr).Decode(&userList) + + if err != nil { + err = fmt.Errorf("problem parsing userlist, %v", err) + } + return userList, err +} diff --git a/user_test.go b/user_test.go new file mode 100644 index 0000000..f89ca62 --- /dev/null +++ b/user_test.go @@ -0,0 +1,51 @@ +package main + +import "testing" + +func TestUpdateUserKey(t *testing.T) { + user := User{ + Username: "bob", + Key: "12345", + IpAddress: "12.12.12.12:50123", + IsAdmin: false, + } + + want := "67890" + + user.UpdateUserKey(want) + if user.Key != want { + t.Errorf("Expected key %v, got %v", want, user.Key) + } +} + +func TestUpdateUserIp(t *testing.T) { + user := User{ + Username: "bob", + Key: "12345", + IpAddress: "12.12.12.12:50123", + IsAdmin: false, + } + + want := "13.13.13.13:50124" + + user.UpdateUserIp(want) + if user.IpAddress != want { + t.Errorf("Expected key %v, got %v", want, user.IpAddress) + } +} + +func TestAddAdmin(t *testing.T) { + user := User{ + Username: "bob", + Key: "12345", + IpAddress: "12.12.12.12:50123", + IsAdmin: false, + } + + want := true + + user.UpdateAdminStatus(want) + if user.IsAdmin != want { + t.Errorf("Expected key %v, got %v", want, user.IsAdmin) + } +}