From 53ddde50c014fdc091041a16e46aeeb15dd8588e Mon Sep 17 00:00:00 2001 From: Gabriel Simmer Date: Sat, 16 Jul 2022 02:00:55 +0100 Subject: [PATCH] Starting to write unit tests! --- .build.yml | 4 +- go.mod | 1 + go.sum | 21 +++ main.go | 4 +- mocks/invite/invite.go | 65 ++++++++ mocks/minecraft/minecraft.go | 5 + mocks/store/database.go | 281 ++++++++++++++++++++++++++++++++++ transport/http.go | 3 +- transport/http_test.go | 289 +++++++++++++++++++++++++++++++++++ 9 files changed, 669 insertions(+), 4 deletions(-) create mode 100644 mocks/invite/invite.go create mode 100644 mocks/minecraft/minecraft.go create mode 100644 mocks/store/database.go create mode 100644 transport/http_test.go diff --git a/.build.yml b/.build.yml index 6ade86f..5c97591 100644 --- a/.build.yml +++ b/.build.yml @@ -11,11 +11,13 @@ packages: sources: - git@git.sr.ht:~gmem/minecraft-server-invites tasks: + - test: | + cd minecraft-server-invites + go test ./... -cover - setup-docker: | sudo touch /etc/subuid /etc/subgid sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 build - build: | - cd minecraft-server-invites podman build . -t icr.gmem.ca/wlm - push: | sudo systemctl start tailscaled diff --git a/go.mod b/go.mod index 5c08a4b..95fd48e 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.18 require ( github.com/Kelwing/mc-rcon v0.0.0-20220214194105-bec8dcbccc3f github.com/alexedwards/flow v0.0.0-20220607190737-c48a87f2b4c4 + github.com/golang/mock v1.6.0 github.com/google/uuid v1.3.0 github.com/mattn/go-sqlite3 v1.14.14 golang.org/x/oauth2 v0.0.0-20220630143837-2104d58473e0 diff --git a/go.sum b/go.sum index c375ca4..3ec2260 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/Kelwing/mc-rcon v0.0.0-20220214194105-bec8dcbccc3f h1:ySopb0/agJ4rwNs github.com/Kelwing/mc-rcon v0.0.0-20220214194105-bec8dcbccc3f/go.mod h1:wlXY/EO9CBjMViEh95JOQhp//RMnHs4ZUBhYhx43LdM= github.com/alexedwards/flow v0.0.0-20220607190737-c48a87f2b4c4 h1:tF8vI7d4pKpByHydzNUDsOuZMOqmuyt+8/HuSN0hzGA= github.com/alexedwards/flow v0.0.0-20220607190737-c48a87f2b4c4/go.mod h1:1rjOQiOqQlmMdUMuvlJFjldqTnE/tQULE7qPIu4aq3U= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= @@ -12,17 +14,36 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20220630143837-2104d58473e0 h1:VnGaRqoLmqZH/3TMLJwYCEWkR4j1nuIU1U9TvbqsDUw= golang.org/x/oauth2 v0.0.0-20220630143837-2104d58473e0/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= diff --git a/main.go b/main.go index e7078ad..8ae7965 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "github.com/alexedwards/flow" "log" "net/http" + "whitelistmanager/invite" "whitelistmanager/store" "whitelistmanager/transport" ) @@ -15,7 +16,8 @@ func main() { panic(err) } defer db.Close() - handlers := transport.New(db) + im := invite.NewManager(db) + handlers := transport.New(db, im) // Auth endpoints mux.Group(func(mux *flow.Mux) { mux.HandleFunc("/api/v1/auth/redirect", handlers.AuthRedirect) diff --git a/mocks/invite/invite.go b/mocks/invite/invite.go new file mode 100644 index 0000000..cb628d1 --- /dev/null +++ b/mocks/invite/invite.go @@ -0,0 +1,65 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: invite/invite.go + +// Package mock_invite is a generated GoMock package. +package mock_invite + +import ( + reflect "reflect" + store "whitelistmanager/store" + + gomock "github.com/golang/mock/gomock" +) + +// MockInviteManager is a mock of InviteManager interface. +type MockInviteManager struct { + ctrl *gomock.Controller + recorder *MockInviteManagerMockRecorder +} + +// MockInviteManagerMockRecorder is the mock recorder for MockInviteManager. +type MockInviteManagerMockRecorder struct { + mock *MockInviteManager +} + +// NewMockInviteManager creates a new mock instance. +func NewMockInviteManager(ctrl *gomock.Controller) *MockInviteManager { + mock := &MockInviteManager{ctrl: ctrl} + mock.recorder = &MockInviteManagerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockInviteManager) EXPECT() *MockInviteManagerMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockInviteManager) Create(in store.Invite, user store.User) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", in, user) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Create indicates an expected call of Create. +func (mr *MockInviteManagerMockRecorder) Create(in, user interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockInviteManager)(nil).Create), in, user) +} + +// RemainingUses mocks base method. +func (m *MockInviteManager) RemainingUses(in store.Invite) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemainingUses", in) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RemainingUses indicates an expected call of RemainingUses. +func (mr *MockInviteManagerMockRecorder) RemainingUses(in interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemainingUses", reflect.TypeOf((*MockInviteManager)(nil).RemainingUses), in) +} diff --git a/mocks/minecraft/minecraft.go b/mocks/minecraft/minecraft.go new file mode 100644 index 0000000..f483420 --- /dev/null +++ b/mocks/minecraft/minecraft.go @@ -0,0 +1,5 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: minecraft/minecraft.go + +// Package mock_minecraft is a generated GoMock package. +package mock_minecraft diff --git a/mocks/store/database.go b/mocks/store/database.go new file mode 100644 index 0000000..db42107 --- /dev/null +++ b/mocks/store/database.go @@ -0,0 +1,281 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: store/database.go + +// Package mock_store is a generated GoMock package. +package mock_store + +import ( + reflect "reflect" + store "whitelistmanager/store" + + gomock "github.com/golang/mock/gomock" +) + +// MockStorer is a mock of Storer interface. +type MockStorer struct { + ctrl *gomock.Controller + recorder *MockStorerMockRecorder +} + +// MockStorerMockRecorder is the mock recorder for MockStorer. +type MockStorerMockRecorder struct { + mock *MockStorer +} + +// NewMockStorer creates a new mock instance. +func NewMockStorer(ctrl *gomock.Controller) *MockStorer { + mock := &MockStorer{ctrl: ctrl} + mock.recorder = &MockStorerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStorer) EXPECT() *MockStorerMockRecorder { + return m.recorder +} + +// Close mocks base method. +func (m *MockStorer) Close() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Close") + ret0, _ := ret[0].(error) + return ret0 +} + +// Close indicates an expected call of Close. +func (mr *MockStorerMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockStorer)(nil).Close)) +} + +// DeleteInvite mocks base method. +func (m *MockStorer) DeleteInvite(invite store.Invite) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteInvite", invite) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteInvite indicates an expected call of DeleteInvite. +func (mr *MockStorerMockRecorder) DeleteInvite(invite interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteInvite", reflect.TypeOf((*MockStorer)(nil).DeleteInvite), invite) +} + +// DeleteServer mocks base method. +func (m *MockStorer) DeleteServer(server store.Server, user store.User) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteServer", server, user) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteServer indicates an expected call of DeleteServer. +func (mr *MockStorerMockRecorder) DeleteServer(server, user interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteServer", reflect.TypeOf((*MockStorer)(nil).DeleteServer), server, user) +} + +// GetInvite mocks base method. +func (m *MockStorer) GetInvite(token string) (store.Invite, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInvite", token) + ret0, _ := ret[0].(store.Invite) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInvite indicates an expected call of GetInvite. +func (mr *MockStorerMockRecorder) GetInvite(token interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInvite", reflect.TypeOf((*MockStorer)(nil).GetInvite), token) +} + +// GetServer mocks base method. +func (m *MockStorer) GetServer(id string) (store.Server, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetServer", id) + ret0, _ := ret[0].(store.Server) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetServer indicates an expected call of GetServer. +func (mr *MockStorerMockRecorder) GetServer(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServer", reflect.TypeOf((*MockStorer)(nil).GetServer), id) +} + +// GetUser mocks base method. +func (m *MockStorer) GetUser(uid string) (store.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUser", uid) + ret0, _ := ret[0].(store.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUser indicates an expected call of GetUser. +func (mr *MockStorerMockRecorder) GetUser(uid interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUser", reflect.TypeOf((*MockStorer)(nil).GetUser), uid) +} + +// GetUserServers mocks base method. +func (m *MockStorer) GetUserServers(user store.User) ([]store.Server, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserServers", user) + ret0, _ := ret[0].([]store.Server) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserServers indicates an expected call of GetUserServers. +func (mr *MockStorerMockRecorder) GetUserServers(user interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserServers", reflect.TypeOf((*MockStorer)(nil).GetUserServers), user) +} + +// InviteLog mocks base method. +func (m *MockStorer) InviteLog(invite store.Invite) ([]store.InviteLog, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InviteLog", invite) + ret0, _ := ret[0].([]store.InviteLog) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// InviteLog indicates an expected call of InviteLog. +func (mr *MockStorerMockRecorder) InviteLog(invite interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InviteLog", reflect.TypeOf((*MockStorer)(nil).InviteLog), invite) +} + +// LogInviteUse mocks base method. +func (m *MockStorer) LogInviteUse(user store.User, invite store.Invite) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogInviteUse", user, invite) + ret0, _ := ret[0].(error) + return ret0 +} + +// LogInviteUse indicates an expected call of LogInviteUse. +func (mr *MockStorerMockRecorder) LogInviteUse(user, invite interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogInviteUse", reflect.TypeOf((*MockStorer)(nil).LogInviteUse), user, invite) +} + +// OauthState mocks base method. +func (m *MockStorer) OauthState(id string) (store.OauthState, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OauthState", id) + ret0, _ := ret[0].(store.OauthState) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// OauthState indicates an expected call of OauthState. +func (mr *MockStorerMockRecorder) OauthState(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OauthState", reflect.TypeOf((*MockStorer)(nil).OauthState), id) +} + +// SaveInvite mocks base method. +func (m *MockStorer) SaveInvite(invite store.Invite) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SaveInvite", invite) + ret0, _ := ret[0].(error) + return ret0 +} + +// SaveInvite indicates an expected call of SaveInvite. +func (mr *MockStorerMockRecorder) SaveInvite(invite interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveInvite", reflect.TypeOf((*MockStorer)(nil).SaveInvite), invite) +} + +// SaveOauthState mocks base method. +func (m *MockStorer) SaveOauthState(state store.OauthState) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SaveOauthState", state) + ret0, _ := ret[0].(error) + return ret0 +} + +// SaveOauthState indicates an expected call of SaveOauthState. +func (mr *MockStorerMockRecorder) SaveOauthState(state interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveOauthState", reflect.TypeOf((*MockStorer)(nil).SaveOauthState), state) +} + +// SaveServer mocks base method. +func (m *MockStorer) SaveServer(server store.Server) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SaveServer", server) + ret0, _ := ret[0].(error) + return ret0 +} + +// SaveServer indicates an expected call of SaveServer. +func (mr *MockStorerMockRecorder) SaveServer(server interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveServer", reflect.TypeOf((*MockStorer)(nil).SaveServer), server) +} + +// SaveSession mocks base method. +func (m *MockStorer) SaveSession(token string, user store.User) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SaveSession", token, user) + ret0, _ := ret[0].(error) + return ret0 +} + +// SaveSession indicates an expected call of SaveSession. +func (mr *MockStorerMockRecorder) SaveSession(token, user interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveSession", reflect.TypeOf((*MockStorer)(nil).SaveSession), token, user) +} + +// SaveUser mocks base method. +func (m *MockStorer) SaveUser(user store.User) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SaveUser", user) + ret0, _ := ret[0].(error) + return ret0 +} + +// SaveUser indicates an expected call of SaveUser. +func (mr *MockStorerMockRecorder) SaveUser(user interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveUser", reflect.TypeOf((*MockStorer)(nil).SaveUser), user) +} + +// ServerInvites mocks base method. +func (m *MockStorer) ServerInvites(server store.Server) ([]store.Invite, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ServerInvites", server) + ret0, _ := ret[0].([]store.Invite) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ServerInvites indicates an expected call of ServerInvites. +func (mr *MockStorerMockRecorder) ServerInvites(server interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServerInvites", reflect.TypeOf((*MockStorer)(nil).ServerInvites), server) +} + +// SessionUser mocks base method. +func (m *MockStorer) SessionUser(token string) (store.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SessionUser", token) + ret0, _ := ret[0].(store.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SessionUser indicates an expected call of SessionUser. +func (mr *MockStorerMockRecorder) SessionUser(token interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SessionUser", reflect.TypeOf((*MockStorer)(nil).SessionUser), token) +} diff --git a/transport/http.go b/transport/http.go index 2d92daf..7a4385c 100644 --- a/transport/http.go +++ b/transport/http.go @@ -41,8 +41,7 @@ type Handle interface { InviteLog(w http.ResponseWriter, r *http.Request) } -func New(store store.Storer) *Handler { - im := invite.NewManager(store) +func New(store store.Storer, im invite.InviteManager) *Handler { return &Handler{ store: store, manager: im, diff --git a/transport/http_test.go b/transport/http_test.go new file mode 100644 index 0000000..f586f0c --- /dev/null +++ b/transport/http_test.go @@ -0,0 +1,289 @@ +package transport_test + +import ( + "bytes" + "context" + "database/sql" + "encoding/json" + "errors" + "github.com/alexedwards/flow" + "github.com/golang/mock/gomock" + "log" + "net/http" + "net/http/httptest" + "testing" + "time" + "whitelistmanager/invite" + mock_invite "whitelistmanager/mocks/invite" + mock_store "whitelistmanager/mocks/store" + "whitelistmanager/store" + "whitelistmanager/transport" +) + +type invitePayload struct { + Server string `json:"server"` + Unlimited bool `json:"unlimited"` + Uses int `json:"uses"` +} + +func TestInvites(t *testing.T) { + ctrl := gomock.NewController(t) + st := mock_store.NewMockStorer(ctrl) + im := mock_invite.NewMockInviteManager(ctrl) + + user := store.User{ + Id: "1", + Token: "", + DisplayName: "user", + RefreshToken: "", + TokenExpiry: time.Time{}, + } + + server := store.Server{ + Id: "1", + } + + inv := store.Invite{ + Token: "foo", + Server: server, + Uses: 0, + Unlimited: false, + } + + invNoToken := store.Invite{ + Token: "", + Server: store.Server{ + Id: "1", + }, + Uses: 0, + Unlimited: false, + } + + invPayload := invitePayload{ + Server: "1", + Uses: 0, + Unlimited: false, + } + + t.Run("create invite correctly", func(t *testing.T) { + im.EXPECT().Create(invNoToken, user).Return(inv.Token, nil) + handler := transport.New(st, im) + + jsonData, err := json.Marshal(invPayload) + if err != nil { + log.Fatal(err) + } + + req, err := http.NewRequest("POST", "/api/v1/invites", bytes.NewBuffer(jsonData)) + if err != nil { + t.Fatal(err) + } + + ctx := req.Context() + ctx = context.WithValue(ctx, "user", user) + req = req.WithContext(ctx) + rr := httptest.NewRecorder() + h := http.HandlerFunc(handler.CreateInvite) + + h.ServeHTTP(rr, req) + + // Check the status code is what we expect. + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + expected := `foo` + if rr.Body.String() != expected { + t.Errorf("handler returned unexpected body: got %v want %v", + rr.Body.String(), expected) + } + }) + + t.Run("user is not server owner", func(t *testing.T) { + im.EXPECT().Create(invNoToken, user).Return("", errors.New(invite.NotOwnerofServer)) + handler := transport.New(st, im) + + jsonData, err := json.Marshal(invPayload) + if err != nil { + log.Fatal(err) + } + + req, err := http.NewRequest("POST", "/api/v1/invites", bytes.NewBuffer(jsonData)) + if err != nil { + t.Fatal(err) + } + + ctx := req.Context() + ctx = context.WithValue(ctx, "user", user) + req = req.WithContext(ctx) + rr := httptest.NewRecorder() + h := http.HandlerFunc(handler.CreateInvite) + + h.ServeHTTP(rr, req) + + // Check the status code is what we expect. + if status := rr.Code; status != http.StatusForbidden { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusForbidden) + } + + expected := "user is not owner of server\n" + if rr.Body.String() != expected { + t.Errorf("handler returned unexpected body: got %v want %v", + rr.Body.String(), expected) + } + }) + + t.Run("get existing invite", func(t *testing.T) { + m := flow.New() + st.EXPECT().GetInvite(inv.Token).Return(inv, nil) + handler := transport.New(st, im) + + req, err := http.NewRequest("GET", "/api/v1/invite/foo", nil) + if err != nil { + t.Fatal(err) + } + + ctx := req.Context() + ctx = context.WithValue(ctx, "user", user) + req = req.WithContext(ctx) + rr := httptest.NewRecorder() + m.HandleFunc("/api/v1/invite/:id", handler.GetInvite) + m.ServeHTTP(rr, req) + + // Check the status code is what we expect. + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + expected, err := json.Marshal(inv) + if err != nil { + t.Fatal(err) + } + if rr.Body.String() != string(expected)+"\n" { + t.Errorf("handler returned unexpected body: got %v want %v", + rr.Body.String(), string(expected)) + } + }) + + t.Run("existing invite", func(t *testing.T) { + m := flow.New() + st.EXPECT().GetInvite(inv.Token).Return(inv, nil) + handler := transport.New(st, im) + + req, err := http.NewRequest("GET", "/api/v1/invite/foo", nil) + if err != nil { + t.Fatal(err) + } + + ctx := req.Context() + ctx = context.WithValue(ctx, "user", user) + req = req.WithContext(ctx) + rr := httptest.NewRecorder() + m.HandleFunc("/api/v1/invite/:id", handler.GetInvite) + m.ServeHTTP(rr, req) + + // Check the status code is what we expect. + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + expected, err := json.Marshal(inv) + if err != nil { + t.Fatal(err) + } + if rr.Body.String() != string(expected)+"\n" { + t.Errorf("handler returned unexpected body: got %v want %v", + rr.Body.String(), string(expected)) + } + }) + + t.Run("non existent invite", func(t *testing.T) { + m := flow.New() + st.EXPECT().GetInvite(inv.Token).Return(store.Invite{}, sql.ErrNoRows) + handler := transport.New(st, im) + + req, err := http.NewRequest("GET", "/api/v1/invite/foo", nil) + if err != nil { + t.Fatal(err) + } + + ctx := req.Context() + ctx = context.WithValue(ctx, "user", user) + req = req.WithContext(ctx) + rr := httptest.NewRecorder() + m.HandleFunc("/api/v1/invite/:id", handler.GetInvite) + m.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusNotFound { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusNotFound) + } + }) + + t.Run("user not logged in when getting invite", func(t *testing.T) { + m := flow.New() + st.EXPECT().GetInvite(inv.Token).Return(inv, nil) + handler := transport.New(st, im) + + req, err := http.NewRequest("GET", "/api/v1/invite/foo", nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + m.HandleFunc("/api/v1/invite/:id", handler.GetInvite) + m.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusNotFound) + } + + expected, err := json.Marshal(inv) + if err != nil { + t.Fatal(err) + } + if rr.Body.String() != string(expected)+"\n" { + t.Errorf("handler returned unexpected body: got %v want %v", + rr.Body.String(), string(expected)) + } + }) + + //t.Run("user can accept invite", func(t *testing.T) { + // m := flow.New() + // st.EXPECT().GetInvite(inv.Token).Return(inv, nil) + // im.EXPECT().RemainingUses(inv).Return(1, nil) + // st.EXPECT().GetServer(server.Id).Return(server, nil) + // handler := transport.New(st, im) + // + // req, err := http.NewRequest("POST", "/api/v1/invite/foo/accept", nil) + // if err != nil { + // t.Fatal(err) + // } + // + // ctx := req.Context() + // ctx = context.WithValue(ctx, "user", user) + // req = req.WithContext(ctx) + // rr := httptest.NewRecorder() + // m.HandleFunc("/api/v1/invite/:id/accept", handler.AcceptInvite, "POST") + // m.ServeHTTP(rr, req) + // + // if status := rr.Code; status != http.StatusForbidden { + // t.Errorf("handler returned wrong status code: got %v want %v", + // status, http.StatusNotFound) + // } + // + // expected, err := json.Marshal(inv) + // if err != nil { + // t.Fatal(err) + // } + // if rr.Body.String() != string(expected)+"\n" { + // t.Errorf("handler returned unexpected body: got %v want %v", + // rr.Body.String(), string(expected)) + // } + }) +}