forked from Silverfish/proton-bridge
GODT-1351: Cache and update of space bytes in user object.
This commit is contained in:
@ -42,7 +42,7 @@ Item {
|
|||||||
|
|
||||||
function spaceWithUnits(bytes){
|
function spaceWithUnits(bytes){
|
||||||
if (bytes*1 !== bytes ) return "0 kB"
|
if (bytes*1 !== bytes ) return "0 kB"
|
||||||
var units = ['B',"kB", "MB", "TB"];
|
var units = ['B',"kB", "MB", "GB", "TB"];
|
||||||
var i = parseInt(Math.floor(Math.log(bytes)/Math.log(1024)));
|
var i = parseInt(Math.floor(Math.log(bytes)/Math.log(1024)));
|
||||||
|
|
||||||
return Math.round(bytes*10 / Math.pow(1024, i))/10 + " " + units[i]
|
return Math.round(bytes*10 / Math.pow(1024, i))/10 + " " + units[i]
|
||||||
|
|||||||
@ -287,8 +287,8 @@ func (qu *QMLUser) update(user types.User) {
|
|||||||
qu.SetLoggedIn(user.IsConnected())
|
qu.SetLoggedIn(user.IsConnected())
|
||||||
qu.SetSplitMode(!user.IsCombinedAddressMode())
|
qu.SetSplitMode(!user.IsCombinedAddressMode())
|
||||||
qu.SetSetupGuideSeen(false)
|
qu.SetSetupGuideSeen(false)
|
||||||
qu.SetUsedBytes(1.0) // TODO
|
qu.SetUsedBytes(float32(user.UsedBytes()))
|
||||||
qu.SetTotalBytes(10000.0) // TODO
|
qu.SetTotalBytes(float32(user.TotalBytes()))
|
||||||
qu.SetPassword(user.GetBridgePassword())
|
qu.SetPassword(user.GetBridgePassword())
|
||||||
qu.SetAddresses(user.GetAddresses())
|
qu.SetAddresses(user.GetAddresses())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,6 +60,8 @@ type UserManager interface {
|
|||||||
// User is an interface of user needed by frontend.
|
// User is an interface of user needed by frontend.
|
||||||
type User interface {
|
type User interface {
|
||||||
ID() string
|
ID() string
|
||||||
|
UsedBytes() int64
|
||||||
|
TotalBytes() int64
|
||||||
Username() string
|
Username() string
|
||||||
IsConnected() bool
|
IsConnected() bool
|
||||||
IsCombinedAddressMode() bool
|
IsCombinedAddressMode() bool
|
||||||
|
|||||||
@ -31,7 +31,7 @@ import (
|
|||||||
|
|
||||||
type storeUserProvider interface {
|
type storeUserProvider interface {
|
||||||
UserID() string
|
UserID() string
|
||||||
GetSpace() (usedSpace, maxSpace uint, err error)
|
GetSpaceKB() (usedSpace, maxSpace uint32, err error)
|
||||||
GetMaxUpload() (int64, error)
|
GetMaxUpload() (int64, error)
|
||||||
|
|
||||||
GetAddress(addressID string) (storeAddressProvider, error)
|
GetAddress(addressID string) (storeAddressProvider, error)
|
||||||
|
|||||||
@ -225,7 +225,7 @@ func (iu *imapUser) GetQuota(name string) (*imapquota.Status, error) {
|
|||||||
// Called from go-imap in goroutines - we need to handle panics for each function.
|
// Called from go-imap in goroutines - we need to handle panics for each function.
|
||||||
defer iu.panicHandler.HandlePanic()
|
defer iu.panicHandler.HandlePanic()
|
||||||
|
|
||||||
usedSpace, maxSpace, err := iu.storeUser.GetSpace()
|
usedSpace, maxSpace, err := iu.storeUser.GetSpaceKB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed getting quota: ", err)
|
log.Error("Failed getting quota: ", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -233,9 +233,8 @@ func (iu *imapUser) GetQuota(name string) (*imapquota.Status, error) {
|
|||||||
|
|
||||||
resources := make(map[string][2]uint32)
|
resources := make(map[string][2]uint32)
|
||||||
var list [2]uint32
|
var list [2]uint32
|
||||||
// Quota is "in units of 1024 octets" (or KB) and PM returns bytes.
|
list[0] = usedSpace
|
||||||
list[0] = uint32(usedSpace / 1024)
|
list[1] = maxSpace
|
||||||
list[1] = uint32(maxSpace / 1024)
|
|
||||||
resources[imapquota.ResourceStorage] = list
|
resources[imapquota.ResourceStorage] = list
|
||||||
status := &imapquota.Status{
|
status := &imapquota.Status{
|
||||||
Name: "",
|
Name: "",
|
||||||
|
|||||||
@ -337,6 +337,11 @@ func (loop *eventLoop) processEvent(event *pmapi.Event) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if event.User != nil {
|
||||||
|
loop.user.UpdateSpace(event.User)
|
||||||
|
loop.listener.Emit(bridgeEvents.UserRefreshEvent, loop.user.ID())
|
||||||
|
}
|
||||||
|
|
||||||
// One would expect that every event would contain MessageCount as part of
|
// One would expect that every event would contain MessageCount as part of
|
||||||
// the event.Messages, but this is apparently not the case.
|
// the event.Messages, but this is apparently not the case.
|
||||||
// MessageCounts are served on an irregular basis, so we should update and
|
// MessageCounts are served on an irregular basis, so we should update and
|
||||||
|
|||||||
@ -207,6 +207,18 @@ func (mr *MockBridgeUserMockRecorder) Logout() *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockBridgeUser)(nil).Logout))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockBridgeUser)(nil).Logout))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateSpace mocks base method.
|
||||||
|
func (m *MockBridgeUser) UpdateSpace(arg0 *pmapi.User) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "UpdateSpace", arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSpace indicates an expected call of UpdateSpace.
|
||||||
|
func (mr *MockBridgeUserMockRecorder) UpdateSpace(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateSpace", reflect.TypeOf((*MockBridgeUser)(nil).UpdateSpace), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateUser mocks base method.
|
// UpdateUser mocks base method.
|
||||||
func (m *MockBridgeUser) UpdateUser(arg0 context.Context) error {
|
func (m *MockBridgeUser) UpdateUser(arg0 context.Context) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
|||||||
@ -37,6 +37,7 @@ type BridgeUser interface {
|
|||||||
GetStoreAddresses() []string
|
GetStoreAddresses() []string
|
||||||
GetClient() pmapi.Client
|
GetClient() pmapi.Client
|
||||||
UpdateUser(context.Context) error
|
UpdateUser(context.Context) error
|
||||||
|
UpdateSpace(*pmapi.User)
|
||||||
CloseAllConnections()
|
CloseAllConnections()
|
||||||
CloseConnection(string)
|
CloseConnection(string)
|
||||||
Logout() error
|
Logout() error
|
||||||
|
|||||||
@ -17,18 +17,47 @@
|
|||||||
|
|
||||||
package store
|
package store
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
// UserID returns user ID.
|
// UserID returns user ID.
|
||||||
func (store *Store) UserID() string {
|
func (store *Store) UserID() string {
|
||||||
return store.user.ID()
|
return store.user.ID()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSpace returns used and total space in bytes.
|
// GetSpaceKB returns used and total space in kilo bytes (needed for IMAP
|
||||||
func (store *Store) GetSpace() (usedSpace, maxSpace uint, err error) {
|
// Quota. Quota is "in units of 1024 octets" (or KB) and PM returns bytes.
|
||||||
|
func (store *Store) GetSpaceKB() (usedSpace, maxSpace uint32, err error) {
|
||||||
apiUser, err := store.client().CurrentUser(exposeContextForIMAP())
|
apiUser, err := store.client().CurrentUser(exposeContextForIMAP())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
return uint(apiUser.UsedSpace), uint(apiUser.MaxSpace), nil
|
if apiUser.UsedSpace != nil {
|
||||||
|
usedSpace = store.toKBandLimit(*apiUser.UsedSpace, usedSpaceType)
|
||||||
|
}
|
||||||
|
if apiUser.MaxSpace != nil {
|
||||||
|
maxSpace = store.toKBandLimit(*apiUser.MaxSpace, maxSpaceType)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type spaceType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
usedSpaceType = spaceType("used")
|
||||||
|
maxSpaceType = spaceType("max")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (store *Store) toKBandLimit(n int64, space spaceType) uint32 {
|
||||||
|
if n < 0 {
|
||||||
|
log.WithField("space", space).Warning("negative number of bytes")
|
||||||
|
return uint32(0)
|
||||||
|
}
|
||||||
|
n /= 1024
|
||||||
|
if n > math.MaxUint32 {
|
||||||
|
log.WithField("space", space).Warning("too large number of bytes")
|
||||||
|
return uint32(math.MaxUint32)
|
||||||
|
}
|
||||||
|
return uint32(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMaxUpload returns max size of message + all attachments in bytes.
|
// GetMaxUpload returns max size of message + all attachments in bytes.
|
||||||
|
|||||||
@ -49,6 +49,8 @@ type User struct {
|
|||||||
userID string
|
userID string
|
||||||
creds *credentials.Credentials
|
creds *credentials.Credentials
|
||||||
|
|
||||||
|
usedBytes, totalBytes int64
|
||||||
|
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +124,8 @@ func (u *User) connect(client pmapi.Client, creds *credentials.Credentials) erro
|
|||||||
u.store.StartWatcher()
|
u.store.StartWatcher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u.UpdateSpace(nil)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,6 +205,40 @@ func (u *User) ID() string {
|
|||||||
return u.userID
|
return u.userID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UsedBytes returns number of bytes used on server.
|
||||||
|
func (u *User) UsedBytes() int64 {
|
||||||
|
return u.usedBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalBytes returns number of bytes available on server.
|
||||||
|
func (u *User) TotalBytes() int64 {
|
||||||
|
return u.totalBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSpace will update TotalBytes and UsedBytes values from API user. If
|
||||||
|
// pointer is nill it will get fresh user from API. API user can come from
|
||||||
|
// update event which means it doesn't contain all data. Therefore only
|
||||||
|
// positive values will be updated.
|
||||||
|
func (u *User) UpdateSpace(apiUser *pmapi.User) {
|
||||||
|
// If missing get latest pmapi.User from API instead of using cached
|
||||||
|
// values from client.CurrentUser()
|
||||||
|
if apiUser == nil {
|
||||||
|
var err error
|
||||||
|
apiUser, err = u.client.GetUser(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
u.log.WithError(err).Warning("Cannot update user space")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if apiUser.UsedSpace != nil {
|
||||||
|
u.usedBytes = *apiUser.UsedSpace
|
||||||
|
}
|
||||||
|
if apiUser.MaxSpace != nil {
|
||||||
|
u.totalBytes = *apiUser.MaxSpace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Username returns the user's username as found in the user's credentials.
|
// Username returns the user's username as found in the user's credentials.
|
||||||
func (u *User) Username() string {
|
func (u *User) Username() string {
|
||||||
u.lock.RLock()
|
u.lock.RLock()
|
||||||
@ -351,7 +389,7 @@ func (u *User) UpdateUser(ctx context.Context) error {
|
|||||||
defer u.lock.Unlock()
|
defer u.lock.Unlock()
|
||||||
defer u.listener.Emit(events.UserRefreshEvent, u.userID)
|
defer u.listener.Emit(events.UserRefreshEvent, u.userID)
|
||||||
|
|
||||||
_, err := u.client.UpdateUser(ctx)
|
user, err := u.client.UpdateUser(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -367,6 +405,8 @@ func (u *User) UpdateUser(ctx context.Context) error {
|
|||||||
|
|
||||||
u.creds = creds
|
u.creds = creds
|
||||||
|
|
||||||
|
u.UpdateSpace(user)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@ func TestUpdateUser(t *testing.T) {
|
|||||||
defer cleanUpUserData(user)
|
defer cleanUpUserData(user)
|
||||||
|
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
m.pmapiClient.EXPECT().UpdateUser(gomock.Any()).Return(nil, nil),
|
m.pmapiClient.EXPECT().UpdateUser(gomock.Any()).Return(testPMAPIUser, nil),
|
||||||
m.pmapiClient.EXPECT().ReloadKeys(gomock.Any(), testCredentials.MailboxPassword).Return(nil),
|
m.pmapiClient.EXPECT().ReloadKeys(gomock.Any(), testCredentials.MailboxPassword).Return(nil),
|
||||||
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
||||||
|
|
||||||
|
|||||||
@ -115,6 +115,10 @@ func (u *Users) watchEvents() {
|
|||||||
log.WithError(err).Error("Failed to load store after reconnecting")
|
log.WithError(err).Error("Failed to load store after reconnecting")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.totalBytes == 0 {
|
||||||
|
user.UpdateSpace(nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -116,9 +116,14 @@ var (
|
|||||||
IsCombinedAddressMode: false,
|
IsCombinedAddressMode: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usedSpace = int64(1048576)
|
||||||
|
maxSpace = int64(10485760)
|
||||||
|
|
||||||
testPMAPIUser = &pmapi.User{ //nolint[gochecknoglobals]
|
testPMAPIUser = &pmapi.User{ //nolint[gochecknoglobals]
|
||||||
ID: "user",
|
ID: "user",
|
||||||
Name: "username",
|
Name: "username",
|
||||||
|
UsedSpace: &usedSpace,
|
||||||
|
MaxSpace: &maxSpace,
|
||||||
}
|
}
|
||||||
|
|
||||||
testPMAPIUserDisconnected = &pmapi.User{ //nolint[gochecknoglobals]
|
testPMAPIUserDisconnected = &pmapi.User{ //nolint[gochecknoglobals]
|
||||||
@ -297,6 +302,7 @@ func mockInitConnectedUser(t *testing.T, m mocks) {
|
|||||||
// Mock of user initialisation.
|
// Mock of user initialisation.
|
||||||
m.pmapiClient.EXPECT().AddAuthRefreshHandler(gomock.Any())
|
m.pmapiClient.EXPECT().AddAuthRefreshHandler(gomock.Any())
|
||||||
m.pmapiClient.EXPECT().IsUnlocked().Return(true).AnyTimes()
|
m.pmapiClient.EXPECT().IsUnlocked().Return(true).AnyTimes()
|
||||||
|
m.pmapiClient.EXPECT().GetUser(gomock.Any()).Return(testPMAPIUser, nil) // load connected user
|
||||||
|
|
||||||
// Mock of store initialisation.
|
// Mock of store initialisation.
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
|
|||||||
@ -32,6 +32,7 @@ type Client interface {
|
|||||||
AuthDelete(context.Context) error
|
AuthDelete(context.Context) error
|
||||||
AddAuthRefreshHandler(AuthRefreshHandler)
|
AddAuthRefreshHandler(AuthRefreshHandler)
|
||||||
|
|
||||||
|
GetUser(ctx context.Context) (*User, error)
|
||||||
CurrentUser(ctx context.Context) (*User, error)
|
CurrentUser(ctx context.Context) (*User, error)
|
||||||
UpdateUser(ctx context.Context) (*User, error)
|
UpdateUser(ctx context.Context) (*User, error)
|
||||||
Unlock(ctx context.Context, passphrase []byte) (err error)
|
Unlock(ctx context.Context, passphrase []byte) (err error)
|
||||||
|
|||||||
@ -40,7 +40,7 @@ type Event struct {
|
|||||||
// Changes applied to labels.
|
// Changes applied to labels.
|
||||||
Labels []*EventLabel
|
Labels []*EventLabel
|
||||||
// Current user status.
|
// Current user status.
|
||||||
User User
|
User *User
|
||||||
// Changes to addresses.
|
// Changes to addresses.
|
||||||
Addresses []*EventAddress
|
Addresses []*EventAddress
|
||||||
// Messages to show to the user.
|
// Messages to show to the user.
|
||||||
@ -198,17 +198,32 @@ func (c *client) getEvent(ctx context.Context, eventID string, numberOfMergedEve
|
|||||||
// mergeEvents combines an old events and a new events object.
|
// mergeEvents combines an old events and a new events object.
|
||||||
// This is not as simple as just blindly joining the two because some things should only be taken from the new events.
|
// This is not as simple as just blindly joining the two because some things should only be taken from the new events.
|
||||||
func mergeEvents(eventsOld *Event, eventsNew *Event) (mergedEvents *Event) {
|
func mergeEvents(eventsOld *Event, eventsNew *Event) (mergedEvents *Event) {
|
||||||
mergedEvents = &Event{
|
return &Event{
|
||||||
EventID: eventsNew.EventID,
|
EventID: eventsNew.EventID,
|
||||||
Refresh: eventsOld.Refresh | eventsNew.Refresh,
|
Refresh: eventsOld.Refresh | eventsNew.Refresh,
|
||||||
More: eventsNew.More,
|
More: eventsNew.More,
|
||||||
Messages: append(eventsOld.Messages, eventsNew.Messages...),
|
Messages: append(eventsOld.Messages, eventsNew.Messages...),
|
||||||
MessageCounts: append(eventsOld.MessageCounts, eventsNew.MessageCounts...),
|
MessageCounts: append(eventsOld.MessageCounts, eventsNew.MessageCounts...),
|
||||||
Labels: append(eventsOld.Labels, eventsNew.Labels...),
|
Labels: append(eventsOld.Labels, eventsNew.Labels...),
|
||||||
User: eventsNew.User,
|
User: mergeUserEvents(eventsOld.User, eventsNew.User),
|
||||||
Addresses: append(eventsOld.Addresses, eventsNew.Addresses...),
|
Addresses: append(eventsOld.Addresses, eventsNew.Addresses...),
|
||||||
Notices: append(eventsOld.Notices, eventsNew.Notices...),
|
Notices: append(eventsOld.Notices, eventsNew.Notices...),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return
|
|
||||||
|
func mergeUserEvents(userOld, userNew *User) *User {
|
||||||
|
if userNew == nil {
|
||||||
|
return userOld
|
||||||
|
}
|
||||||
|
|
||||||
|
if userOld != nil {
|
||||||
|
if userNew.MaxSpace == nil {
|
||||||
|
userNew.MaxSpace = userOld.MaxSpace
|
||||||
|
}
|
||||||
|
if userNew.UsedSpace == nil {
|
||||||
|
userNew.UsedSpace = userOld.UsedSpace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return userNew
|
||||||
}
|
}
|
||||||
|
|||||||
@ -221,10 +221,11 @@ var (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
User: User{
|
User: &User{
|
||||||
ID: "userID1",
|
ID: "userID1",
|
||||||
Name: "user",
|
Name: "user",
|
||||||
UsedSpace: 23456,
|
UsedSpace: &usedSpace,
|
||||||
|
MaxSpace: &maxSpace,
|
||||||
},
|
},
|
||||||
Addresses: []*EventAddress{
|
Addresses: []*EventAddress{
|
||||||
{
|
{
|
||||||
@ -464,7 +465,8 @@ const (
|
|||||||
"User": {
|
"User": {
|
||||||
"ID": "userID1",
|
"ID": "userID1",
|
||||||
"Name": "user",
|
"Name": "user",
|
||||||
"UsedSpace": 12345
|
"UsedSpace": 12345,
|
||||||
|
"MaxSpace": 12345678
|
||||||
},
|
},
|
||||||
"Addresses": [
|
"Addresses": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -362,6 +362,21 @@ func (mr *MockClientMockRecorder) GetPublicKeysForEmail(arg0, arg1 interface{})
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublicKeysForEmail", reflect.TypeOf((*MockClient)(nil).GetPublicKeysForEmail), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublicKeysForEmail", reflect.TypeOf((*MockClient)(nil).GetPublicKeysForEmail), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUser mocks base method.
|
||||||
|
func (m *MockClient) GetUser(arg0 context.Context) (*pmapi.User, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetUser", arg0)
|
||||||
|
ret0, _ := ret[0].(*pmapi.User)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUser indicates an expected call of GetUser.
|
||||||
|
func (mr *MockClientMockRecorder) GetUser(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUser", reflect.TypeOf((*MockClient)(nil).GetUser), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
// GetUserKeyRing mocks base method.
|
// GetUserKeyRing mocks base method.
|
||||||
func (m *MockClient) GetUserKeyRing() (*crypto.KeyRing, error) {
|
func (m *MockClient) GetUserKeyRing() (*crypto.KeyRing, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
|||||||
@ -3,10 +3,10 @@
|
|||||||
"User": {
|
"User": {
|
||||||
"ID": "MJLke8kWh1BBvG95JBIrZvzpgsZ94hNNgjNHVyhXMiv4g9cn6SgvqiIFR5cigpml2LD_iUk_3DkV29oojTt3eA==",
|
"ID": "MJLke8kWh1BBvG95JBIrZvzpgsZ94hNNgjNHVyhXMiv4g9cn6SgvqiIFR5cigpml2LD_iUk_3DkV29oojTt3eA==",
|
||||||
"Name": "jason",
|
"Name": "jason",
|
||||||
"UsedSpace": 96691332,
|
"UsedSpace": 23456,
|
||||||
"Currency": "USD",
|
"Currency": "USD",
|
||||||
"Credit": 0,
|
"Credit": 0,
|
||||||
"MaxSpace": 10737418240,
|
"MaxSpace": 12345678,
|
||||||
"MaxUpload": 26214400,
|
"MaxUpload": 26214400,
|
||||||
"Role": 2,
|
"Role": 2,
|
||||||
"Private": 1,
|
"Private": 1,
|
||||||
|
|||||||
@ -63,10 +63,10 @@ const (
|
|||||||
type User struct {
|
type User struct {
|
||||||
ID string
|
ID string
|
||||||
Name string
|
Name string
|
||||||
UsedSpace int64
|
UsedSpace *int64
|
||||||
Currency string
|
Currency string
|
||||||
Credit int
|
Credit int
|
||||||
MaxSpace int64
|
MaxSpace *int64
|
||||||
MaxUpload int64
|
MaxUpload int64
|
||||||
Role int
|
Role int
|
||||||
Private int
|
Private int
|
||||||
@ -85,7 +85,7 @@ type User struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) getUser(ctx context.Context) (user *User, err error) {
|
func (c *client) GetUser(ctx context.Context) (user *User, err error) {
|
||||||
var res struct {
|
var res struct {
|
||||||
User *User
|
User *User
|
||||||
}
|
}
|
||||||
@ -114,7 +114,7 @@ func (c *client) unlockUser(passphrase []byte) (err error) {
|
|||||||
|
|
||||||
// UpdateUser retrieves details about user and loads its addresses.
|
// UpdateUser retrieves details about user and loads its addresses.
|
||||||
func (c *client) UpdateUser(ctx context.Context) (*User, error) {
|
func (c *client) UpdateUser(ctx context.Context) (*User, error) {
|
||||||
user, err := c.getUser(ctx)
|
user, err := c.GetUser(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,19 +28,24 @@ import (
|
|||||||
r "github.com/stretchr/testify/require"
|
r "github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testCurrentUser = &User{
|
var (
|
||||||
ID: "MJLke8kWh1BBvG95JBIrZvzpgsZ94hNNgjNHVyhXMiv4g9cn6SgvqiIFR5cigpml2LD_iUk_3DkV29oojTt3eA==",
|
usedSpace = int64(23456)
|
||||||
Name: "jason",
|
maxSpace = int64(12345678)
|
||||||
UsedSpace: 96691332,
|
|
||||||
Currency: "USD",
|
testCurrentUser = &User{
|
||||||
Role: 2,
|
ID: "MJLke8kWh1BBvG95JBIrZvzpgsZ94hNNgjNHVyhXMiv4g9cn6SgvqiIFR5cigpml2LD_iUk_3DkV29oojTt3eA==",
|
||||||
Subscribed: 1,
|
Name: "jason",
|
||||||
Services: 1,
|
UsedSpace: &usedSpace,
|
||||||
MaxSpace: 10737418240,
|
Currency: "USD",
|
||||||
MaxUpload: 26214400,
|
Role: 2,
|
||||||
Private: 1,
|
Subscribed: 1,
|
||||||
Keys: *loadPMKeys(readTestFile("keyring_userKey_JSON", false)),
|
Services: 1,
|
||||||
}
|
MaxSpace: &maxSpace,
|
||||||
|
MaxUpload: 26214400,
|
||||||
|
Private: 1,
|
||||||
|
Keys: *loadPMKeys(readTestFile("keyring_userKey_JSON", false)),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func routeGetUsers(tb testing.TB, w http.ResponseWriter, req *http.Request) string {
|
func routeGetUsers(tb testing.TB, w http.ResponseWriter, req *http.Request) string {
|
||||||
r.NoError(tb, checkMethodAndPath(req, "GET", "/users"))
|
r.NoError(tb, checkMethodAndPath(req, "GET", "/users"))
|
||||||
|
|||||||
@ -3,27 +3,37 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"ID": "1",
|
"ID": "1",
|
||||||
"Name": "user",
|
"Name": "user",
|
||||||
"MaxUpload": 26214400
|
"MaxUpload": 26214400,
|
||||||
|
"UsedSpace": 1048576,
|
||||||
|
"MaxSpace": 10485760
|
||||||
},
|
},
|
||||||
"user2fa": {
|
"user2fa": {
|
||||||
"ID": "2",
|
"ID": "2",
|
||||||
"Name": "user2fa",
|
"Name": "user2fa",
|
||||||
"MaxUpload": 26214400
|
"MaxUpload": 26214400,
|
||||||
|
"UsedSpace": 1048576,
|
||||||
|
"MaxSpace": 10485760
|
||||||
},
|
},
|
||||||
"userAddressWithCapitalLetter": {
|
"userAddressWithCapitalLetter": {
|
||||||
"ID": "3",
|
"ID": "3",
|
||||||
"Name": "userAddressWithCapitalLetter",
|
"Name": "userAddressWithCapitalLetter",
|
||||||
"MaxUpload": 26214400
|
"MaxUpload": 26214400,
|
||||||
|
"UsedSpace": 1048576,
|
||||||
|
"MaxSpace": 10485760
|
||||||
},
|
},
|
||||||
"userMoreAddresses": {
|
"userMoreAddresses": {
|
||||||
"ID": "4",
|
"ID": "4",
|
||||||
"Name": "userMoreAddresses",
|
"Name": "userMoreAddresses",
|
||||||
"MaxUpload": 26214400
|
"MaxUpload": 26214400,
|
||||||
|
"UsedSpace": 1048576,
|
||||||
|
"MaxSpace": 10485760
|
||||||
},
|
},
|
||||||
"userDisabledPrimaryAddress": {
|
"userDisabledPrimaryAddress": {
|
||||||
"ID": "5",
|
"ID": "5",
|
||||||
"Name": "userDisabledPrimaryAddress",
|
"Name": "userDisabledPrimaryAddress",
|
||||||
"MaxUpload": 26214400
|
"MaxUpload": 26214400,
|
||||||
|
"UsedSpace": 1048576,
|
||||||
|
"MaxSpace": 10485760
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"addresses": {
|
"addresses": {
|
||||||
|
|||||||
@ -85,6 +85,14 @@ func (api *FakePMAPI) UpdateUser(context.Context) (*pmapi.User, error) {
|
|||||||
return api.user, nil
|
return api.user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *FakePMAPI) GetUser(ctx context.Context) (*pmapi.User, error) {
|
||||||
|
if err := api.checkAndRecordCall(GET, "/users", nil); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.user, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (api *FakePMAPI) GetAddresses(context.Context) (pmapi.AddressList, error) {
|
func (api *FakePMAPI) GetAddresses(context.Context) (pmapi.AddressList, error) {
|
||||||
if err := api.checkAndRecordCall(GET, "/addresses", nil); err != nil {
|
if err := api.checkAndRecordCall(GET, "/addresses", nil); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@ -6,6 +6,7 @@ Feature: Start bridge
|
|||||||
Then "user" is connected
|
Then "user" is connected
|
||||||
And "user" has loaded store
|
And "user" has loaded store
|
||||||
And "user" has running event loop
|
And "user" has running event loop
|
||||||
|
And "user" has non-zero space
|
||||||
|
|
||||||
Scenario: Start with connected user, database file and no internet connection
|
Scenario: Start with connected user, database file and no internet connection
|
||||||
Given there is user "user" which just logged in
|
Given there is user "user" which just logged in
|
||||||
@ -15,6 +16,7 @@ Feature: Start bridge
|
|||||||
Then "user" is connected
|
Then "user" is connected
|
||||||
And "user" has loaded store
|
And "user" has loaded store
|
||||||
And "user" has running event loop
|
And "user" has running event loop
|
||||||
|
And "user" has zero space
|
||||||
|
|
||||||
Scenario: Start with connected user, no database file and internet connection
|
Scenario: Start with connected user, no database file and internet connection
|
||||||
Given there is user "user" which just logged in
|
Given there is user "user" which just logged in
|
||||||
@ -23,6 +25,7 @@ Feature: Start bridge
|
|||||||
Then "user" is connected
|
Then "user" is connected
|
||||||
And "user" has loaded store
|
And "user" has loaded store
|
||||||
And "user" has running event loop
|
And "user" has running event loop
|
||||||
|
And "user" has non-zero space
|
||||||
|
|
||||||
Scenario: Start with connected user, no database file and no internet connection
|
Scenario: Start with connected user, no database file and no internet connection
|
||||||
Given there is user "user" which just logged in
|
Given there is user "user" which just logged in
|
||||||
@ -37,6 +40,7 @@ Feature: Start bridge
|
|||||||
Then "user" is connected
|
Then "user" is connected
|
||||||
And "user" has loaded store
|
And "user" has loaded store
|
||||||
And "user" has running event loop
|
And "user" has running event loop
|
||||||
|
And "user" has non-zero space
|
||||||
|
|
||||||
Scenario: Start with disconnected user, database file and internet connection
|
Scenario: Start with disconnected user, database file and internet connection
|
||||||
Given there is disconnected user "user"
|
Given there is disconnected user "user"
|
||||||
@ -45,6 +49,7 @@ Feature: Start bridge
|
|||||||
Then "user" is disconnected
|
Then "user" is disconnected
|
||||||
And "user" has loaded store
|
And "user" has loaded store
|
||||||
And "user" does not have running event loop
|
And "user" does not have running event loop
|
||||||
|
And "user" has zero space
|
||||||
|
|
||||||
Scenario: Start with disconnected user, database file and no internet connection
|
Scenario: Start with disconnected user, database file and no internet connection
|
||||||
Given there is disconnected user "user"
|
Given there is disconnected user "user"
|
||||||
@ -54,6 +59,7 @@ Feature: Start bridge
|
|||||||
Then "user" is disconnected
|
Then "user" is disconnected
|
||||||
And "user" has loaded store
|
And "user" has loaded store
|
||||||
And "user" does not have running event loop
|
And "user" does not have running event loop
|
||||||
|
And "user" has zero space
|
||||||
|
|
||||||
Scenario: Start with disconnected user, no database file and internet connection
|
Scenario: Start with disconnected user, no database file and internet connection
|
||||||
Given there is disconnected user "user"
|
Given there is disconnected user "user"
|
||||||
@ -62,6 +68,7 @@ Feature: Start bridge
|
|||||||
Then "user" is disconnected
|
Then "user" is disconnected
|
||||||
And "user" does not have loaded store
|
And "user" does not have loaded store
|
||||||
And "user" does not have running event loop
|
And "user" does not have running event loop
|
||||||
|
And "user" has zero space
|
||||||
|
|
||||||
Scenario: Start with disconnected user, no database file and no internet connection
|
Scenario: Start with disconnected user, no database file and no internet connection
|
||||||
Given there is disconnected user "user"
|
Given there is disconnected user "user"
|
||||||
@ -71,3 +78,4 @@ Feature: Start bridge
|
|||||||
Then "user" is disconnected
|
Then "user" is disconnected
|
||||||
And "user" does not have loaded store
|
And "user" does not have loaded store
|
||||||
And "user" does not have running event loop
|
And "user" does not have running event loop
|
||||||
|
And "user" has zero space
|
||||||
|
|||||||
@ -6,6 +6,7 @@ Feature: Login for the first time
|
|||||||
And "user" is connected
|
And "user" is connected
|
||||||
And "user" has database file
|
And "user" has database file
|
||||||
And "user" has running event loop
|
And "user" has running event loop
|
||||||
|
And "user" has non-zero space
|
||||||
|
|
||||||
@ignore-live
|
@ignore-live
|
||||||
Scenario: Login with bad username
|
Scenario: Login with bad username
|
||||||
@ -31,6 +32,7 @@ Feature: Login for the first time
|
|||||||
And "user2fa" is connected
|
And "user2fa" is connected
|
||||||
And "user2fa" has database file
|
And "user2fa" has database file
|
||||||
And "user2fa" has running event loop
|
And "user2fa" has running event loop
|
||||||
|
And "user2fa" has non-zero space
|
||||||
|
|
||||||
Scenario: Login user with capital letters in address
|
Scenario: Login user with capital letters in address
|
||||||
Given there is user "userAddressWithCapitalLetter"
|
Given there is user "userAddressWithCapitalLetter"
|
||||||
@ -39,6 +41,7 @@ Feature: Login for the first time
|
|||||||
And "userAddressWithCapitalLetter" is connected
|
And "userAddressWithCapitalLetter" is connected
|
||||||
And "userAddressWithCapitalLetter" has database file
|
And "userAddressWithCapitalLetter" has database file
|
||||||
And "userAddressWithCapitalLetter" has running event loop
|
And "userAddressWithCapitalLetter" has running event loop
|
||||||
|
And "userAddressWithCapitalLetter" has non-zero space
|
||||||
|
|
||||||
Scenario: Login user with more addresses
|
Scenario: Login user with more addresses
|
||||||
Given there is user "userMoreAddresses"
|
Given there is user "userMoreAddresses"
|
||||||
@ -47,6 +50,7 @@ Feature: Login for the first time
|
|||||||
And "userMoreAddresses" is connected
|
And "userMoreAddresses" is connected
|
||||||
And "userMoreAddresses" has database file
|
And "userMoreAddresses" has database file
|
||||||
And "userMoreAddresses" has running event loop
|
And "userMoreAddresses" has running event loop
|
||||||
|
And "userMoreAddresses" has non-zero space
|
||||||
|
|
||||||
@ignore-live
|
@ignore-live
|
||||||
Scenario: Login user with disabled primary address
|
Scenario: Login user with disabled primary address
|
||||||
@ -56,6 +60,7 @@ Feature: Login for the first time
|
|||||||
And "userDisabledPrimaryAddress" is connected
|
And "userDisabledPrimaryAddress" is connected
|
||||||
And "userDisabledPrimaryAddress" has database file
|
And "userDisabledPrimaryAddress" has database file
|
||||||
And "userDisabledPrimaryAddress" has running event loop
|
And "userDisabledPrimaryAddress" has running event loop
|
||||||
|
And "userDisabledPrimaryAddress" has non-zero space
|
||||||
|
|
||||||
Scenario: Login two users
|
Scenario: Login two users
|
||||||
Given there is user "user"
|
Given there is user "user"
|
||||||
|
|||||||
@ -6,6 +6,7 @@ Feature: Re-login
|
|||||||
Then last response is "failed to finish login: user is already connected"
|
Then last response is "failed to finish login: user is already connected"
|
||||||
And "user" is connected
|
And "user" is connected
|
||||||
And "user" has running event loop
|
And "user" has running event loop
|
||||||
|
And "user" has non-zero space
|
||||||
|
|
||||||
@ignore
|
@ignore
|
||||||
Scenario: Re-login with connected user and no database file
|
Scenario: Re-login with connected user and no database file
|
||||||
@ -24,6 +25,7 @@ Feature: Re-login
|
|||||||
Then last response is "OK"
|
Then last response is "OK"
|
||||||
And "user" is connected
|
And "user" is connected
|
||||||
And "user" has running event loop
|
And "user" has running event loop
|
||||||
|
And "user" has non-zero space
|
||||||
|
|
||||||
Scenario: Re-login with disconnected user and no database file
|
Scenario: Re-login with disconnected user and no database file
|
||||||
Given there is disconnected user "user"
|
Given there is disconnected user "user"
|
||||||
@ -33,3 +35,4 @@ Feature: Re-login
|
|||||||
And "user" is connected
|
And "user" is connected
|
||||||
And "user" has database file
|
And "user" has database file
|
||||||
And "user" has running event loop
|
And "user" has running event loop
|
||||||
|
And "user" has non-zero space
|
||||||
|
|||||||
@ -21,7 +21,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ProtonMail/go-srp"
|
"github.com/ProtonMail/go-srp"
|
||||||
@ -84,7 +83,7 @@ func (pc *persistentClient) GetEvent(ctx context.Context, eventID string) (*pmap
|
|||||||
}
|
}
|
||||||
|
|
||||||
func SetupPersistentClients() {
|
func SetupPersistentClients() {
|
||||||
app := os.Getenv("TEST_APP")
|
app := "bridge"
|
||||||
|
|
||||||
persistentClients.manager = pmapi.New(pmapi.NewConfig(app, constants.Version))
|
persistentClients.manager = pmapi.New(pmapi.NewConfig(app, constants.Version))
|
||||||
persistentClients.manager.SetLogging(logrus.WithField("pkg", "liveapi"), logrus.GetLevel() == logrus.TraceLevel)
|
persistentClients.manager.SetLogging(logrus.WithField("pkg", "liveapi"), logrus.GetLevel() == logrus.TraceLevel)
|
||||||
|
|||||||
@ -34,6 +34,8 @@ func UsersChecksFeatureContext(s *godog.ScenarioContext) {
|
|||||||
s.Step(`^"([^"]*)" does not have loaded store$`, userDoesNotHaveLoadedStore)
|
s.Step(`^"([^"]*)" does not have loaded store$`, userDoesNotHaveLoadedStore)
|
||||||
s.Step(`^"([^"]*)" has running event loop$`, userHasRunningEventLoop)
|
s.Step(`^"([^"]*)" has running event loop$`, userHasRunningEventLoop)
|
||||||
s.Step(`^"([^"]*)" does not have running event loop$`, userDoesNotHaveRunningEventLoop)
|
s.Step(`^"([^"]*)" does not have running event loop$`, userDoesNotHaveRunningEventLoop)
|
||||||
|
s.Step(`^"([^"]*)" has non-zero space$`, userHasNonZeroSpace)
|
||||||
|
s.Step(`^"([^"]*)" has zero space$`, userHasZeroSpace)
|
||||||
}
|
}
|
||||||
|
|
||||||
func userHasAddressModeInMode(bddUserID, wantAddressMode string) error {
|
func userHasAddressModeInMode(bddUserID, wantAddressMode string) error {
|
||||||
@ -162,3 +164,26 @@ func userDoesNotHaveRunningEventLoop(bddUserID string) error {
|
|||||||
}, 5*time.Second, 10*time.Millisecond)
|
}, 5*time.Second, 10*time.Millisecond)
|
||||||
return ctx.GetTestingError()
|
return ctx.GetTestingError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func userHasSpace(bddUserID string, wantNonZero bool) error {
|
||||||
|
account := ctx.GetTestAccount(bddUserID)
|
||||||
|
if account == nil {
|
||||||
|
return godog.ErrPending
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := ctx.GetUser(account.Username())
|
||||||
|
if err != nil {
|
||||||
|
return internalError(err, "getting user %s", account.Username())
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should only test `user.TotalBytes()>0` and not test
|
||||||
|
// `user.UsedBytes()>0`. The later value can be always zero when
|
||||||
|
// account is empty even when user object had already cached the value
|
||||||
|
// from server.
|
||||||
|
a.Equal(ctx.GetTestingT(), wantNonZero, user.TotalBytes() > 0)
|
||||||
|
|
||||||
|
return ctx.GetTestingError()
|
||||||
|
}
|
||||||
|
|
||||||
|
func userHasNonZeroSpace(bddUserID string) error { return userHasSpace(bddUserID, true) }
|
||||||
|
func userHasZeroSpace(bddUserID string) error { return userHasSpace(bddUserID, false) }
|
||||||
|
|||||||
Reference in New Issue
Block a user