1
0
Fork 0
mirror of https://github.com/anyproto/anytype-heart.git synced 2025-06-08 05:47:07 +09:00

GO-4459: Refactor auth tests

This commit is contained in:
Jannis Metrikat 2024-12-31 12:39:13 +01:00
parent d05cc8a257
commit 239a606eb3
No known key found for this signature in database
GPG key ID: B223CAC5AAF85615
5 changed files with 148 additions and 399 deletions

View file

@ -52,7 +52,7 @@ func AuthTokenHandler(s *AuthService) gin.HandlerFunc {
sessionToken, appKey, err := s.SolveChallengeForToken(c.Request.Context(), challengeID, code)
errCode := util.MapErrorCode(err,
util.ErrToCode(ErrInvalidInput, http.StatusBadRequest),
util.ErrToCode(ErrorFailedAuthenticate, http.StatusInternalServerError),
util.ErrToCode(ErrFailedAuthenticate, http.StatusInternalServerError),
)
if errCode != http.StatusOK {

View file

@ -11,7 +11,7 @@ import (
var (
ErrFailedGenerateChallenge = errors.New("failed to generate a new challenge")
ErrInvalidInput = errors.New("invalid input")
ErrorFailedAuthenticate = errors.New("failed to authenticate user")
ErrFailedAuthenticate = errors.New("failed to authenticate user")
)
type Service interface {
@ -53,7 +53,7 @@ func (s *AuthService) SolveChallengeForToken(ctx context.Context, challengeID, c
})
if resp.Error.Code != pb.RpcAccountLocalLinkSolveChallengeResponseError_NULL {
return "", "", ErrorFailedAuthenticate
return "", "", ErrFailedAuthenticate
}
return resp.SessionToken, resp.AppKey, nil

View file

@ -0,0 +1,140 @@
package auth
import (
"context"
"testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/anyproto/anytype-heart/pb"
"github.com/anyproto/anytype-heart/pb/service/mock_service"
)
const (
mockedAppName = "api-test"
mockedChallengeId = "mocked-challenge-id"
mockedCode = "mocked-mockedCode"
mockedSessionToken = "mocked-session-token"
mockedAppKey = "mocked-app-key"
)
type fixture struct {
*AuthService
mwMock *mock_service.MockClientCommandsServer
}
func newFixture(t *testing.T) *fixture {
mw := mock_service.NewMockClientCommandsServer(t)
authService := NewService(mw)
return &fixture{
AuthService: authService,
mwMock: mw,
}
}
func TestAuthService_GenerateNewChallenge(t *testing.T) {
t.Run("successful challenge creation", func(t *testing.T) {
// given
ctx := context.Background()
fx := newFixture(t)
fx.mwMock.On("AccountLocalLinkNewChallenge", mock.Anything, &pb.RpcAccountLocalLinkNewChallengeRequest{AppName: mockedAppName}).
Return(&pb.RpcAccountLocalLinkNewChallengeResponse{
ChallengeId: mockedChallengeId,
Error: &pb.RpcAccountLocalLinkNewChallengeResponseError{Code: pb.RpcAccountLocalLinkNewChallengeResponseError_NULL},
}).Once()
// when
challengeId, err := fx.GenerateNewChallenge(ctx, mockedAppName)
// then
require.NoError(t, err)
require.Equal(t, mockedChallengeId, challengeId)
})
t.Run("failed challenge creation", func(t *testing.T) {
// given
ctx := context.Background()
fx := newFixture(t)
fx.mwMock.On("AccountLocalLinkNewChallenge", mock.Anything, &pb.RpcAccountLocalLinkNewChallengeRequest{AppName: mockedAppName}).
Return(&pb.RpcAccountLocalLinkNewChallengeResponse{
Error: &pb.RpcAccountLocalLinkNewChallengeResponseError{Code: pb.RpcAccountLocalLinkNewChallengeResponseError_UNKNOWN_ERROR},
}).Once()
// when
challengeId, err := fx.GenerateNewChallenge(ctx, mockedAppName)
// then
require.Error(t, err)
require.Equal(t, ErrFailedGenerateChallenge, err)
require.Empty(t, challengeId)
})
}
func TestAuthService_SolveChallengeForToken(t *testing.T) {
t.Run("successful token retrieval", func(t *testing.T) {
// given
ctx := context.Background()
fx := newFixture(t)
fx.mwMock.On("AccountLocalLinkSolveChallenge", mock.Anything, &pb.RpcAccountLocalLinkSolveChallengeRequest{
ChallengeId: mockedChallengeId,
Answer: mockedCode,
}).
Return(&pb.RpcAccountLocalLinkSolveChallengeResponse{
SessionToken: mockedSessionToken,
AppKey: mockedAppKey,
Error: &pb.RpcAccountLocalLinkSolveChallengeResponseError{Code: pb.RpcAccountLocalLinkSolveChallengeResponseError_NULL},
}).Once()
// when
sessionToken, appKey, err := fx.SolveChallengeForToken(ctx, mockedChallengeId, mockedCode)
// then
require.NoError(t, err)
require.Equal(t, mockedSessionToken, sessionToken)
require.Equal(t, mockedAppKey, appKey)
})
t.Run("bad request", func(t *testing.T) {
// given
ctx := context.Background()
fx := newFixture(t)
// when
sessionToken, appKey, err := fx.SolveChallengeForToken(ctx, "", "")
// then
require.Error(t, err)
require.Equal(t, ErrInvalidInput, err)
require.Empty(t, sessionToken)
require.Empty(t, appKey)
})
t.Run("failed token retrieval", func(t *testing.T) {
// given
ctx := context.Background()
fx := newFixture(t)
fx.mwMock.On("AccountLocalLinkSolveChallenge", mock.Anything, &pb.RpcAccountLocalLinkSolveChallengeRequest{
ChallengeId: mockedChallengeId,
Answer: mockedCode,
}).
Return(&pb.RpcAccountLocalLinkSolveChallengeResponse{
Error: &pb.RpcAccountLocalLinkSolveChallengeResponseError{Code: pb.RpcAccountLocalLinkSolveChallengeResponseError_UNKNOWN_ERROR},
}).Once()
// when
sessionToken, appKey, err := fx.SolveChallengeForToken(ctx, mockedChallengeId, mockedCode)
// then
require.Error(t, err)
require.Equal(t, ErrFailedAuthenticate, err)
require.Empty(t, sessionToken)
require.Empty(t, appKey)
})
}

View file

@ -85,401 +85,6 @@ func newFixture(t *testing.T) *fixture {
}
}
func TestApiServer_AuthDisplayCodeHandler(t *testing.T) {
t.Run("successful challenge creation", func(t *testing.T) {
// given
fx := newFixture(t)
fx.mwMock.On("AccountLocalLinkNewChallenge", mock.Anything, &pb.RpcAccountLocalLinkNewChallengeRequest{AppName: "api-test"}).
Return(&pb.RpcAccountLocalLinkNewChallengeResponse{
ChallengeId: "mocked-challenge-id",
Error: &pb.RpcAccountLocalLinkNewChallengeResponseError{Code: pb.RpcAccountLocalLinkNewChallengeResponseError_NULL},
}).Once()
// when
req, _ := http.NewRequest("POST", "/v1/auth/displayCode", nil)
w := httptest.NewRecorder()
fx.router.ServeHTTP(w, req)
// then
require.Equal(t, http.StatusOK, w.Code)
var response AuthDisplayCodeResponse
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
require.Equal(t, "mocked-challenge-id", response.ChallengeId)
})
t.Run("failed challenge creation", func(t *testing.T) {
// given
fx := newFixture(t)
fx.mwMock.On("AccountLocalLinkNewChallenge", mock.Anything, &pb.RpcAccountLocalLinkNewChallengeRequest{AppName: "api-test"}).
Return(&pb.RpcAccountLocalLinkNewChallengeResponse{
Error: &pb.RpcAccountLocalLinkNewChallengeResponseError{Code: pb.RpcAccountLocalLinkNewChallengeResponseError_UNKNOWN_ERROR},
}).Once()
// when
req, _ := http.NewRequest("POST", "/v1/auth/displayCode", nil)
w := httptest.NewRecorder()
fx.router.ServeHTTP(w, req)
// then
require.Equal(t, http.StatusInternalServerError, w.Code)
})
}
func TestApiServer_AuthTokenHandler(t *testing.T) {
t.Run("successful token retrieval", func(t *testing.T) {
// given
fx := newFixture(t)
challengeId := "mocked-challenge-id"
code := "mocked-code"
sessionToken := "mocked-session-token"
appKey := "mocked-app-key"
fx.mwMock.On("AccountLocalLinkSolveChallenge", mock.Anything, &pb.RpcAccountLocalLinkSolveChallengeRequest{
ChallengeId: challengeId,
Answer: code,
}).
Return(&pb.RpcAccountLocalLinkSolveChallengeResponse{
SessionToken: sessionToken,
AppKey: appKey,
Error: &pb.RpcAccountLocalLinkSolveChallengeResponseError{Code: pb.RpcAccountLocalLinkSolveChallengeResponseError_NULL},
}).Once()
// when
req, _ := http.NewRequest("GET", "/v1/auth/token?challenge_id="+challengeId+"&code="+code, nil)
w := httptest.NewRecorder()
fx.router.ServeHTTP(w, req)
// then
require.Equal(t, http.StatusOK, w.Code)
var response AuthTokenResponse
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
require.Equal(t, sessionToken, response.SessionToken)
require.Equal(t, appKey, response.AppKey)
})
t.Run("bad request", func(t *testing.T) {
// given
fx := newFixture(t)
// when
req, _ := http.NewRequest("GET", "/v1/auth/token", nil)
w := httptest.NewRecorder()
fx.router.ServeHTTP(w, req)
// then
require.Equal(t, http.StatusBadRequest, w.Code)
})
t.Run("failed token retrieval", func(t *testing.T) {
// given
fx := newFixture(t)
challengeId := "mocked-challenge-id"
code := "mocked-code"
fx.mwMock.On("AccountLocalLinkSolveChallenge", mock.Anything, &pb.RpcAccountLocalLinkSolveChallengeRequest{
ChallengeId: challengeId,
Answer: code,
}).
Return(&pb.RpcAccountLocalLinkSolveChallengeResponse{
Error: &pb.RpcAccountLocalLinkSolveChallengeResponseError{Code: pb.RpcAccountLocalLinkSolveChallengeResponseError_UNKNOWN_ERROR},
}).Once()
// when
req, _ := http.NewRequest("GET", "/v1/auth/token?challenge_id="+challengeId+"&code="+code, nil)
w := httptest.NewRecorder()
fx.router.ServeHTTP(w, req)
// then
require.Equal(t, http.StatusInternalServerError, w.Code)
})
}
func TestApiServer_GetSpacesHandler(t *testing.T) {
t.Run("successful retrieval of spaces", func(t *testing.T) {
// given
fx := newFixture(t)
fx.accountInfo = &model.AccountInfo{TechSpaceId: techSpaceId, GatewayUrl: gatewayUrl}
fx.mwMock.On("ObjectSearch", mock.Anything, &pb.RpcObjectSearchRequest{
SpaceId: techSpaceId,
Filters: []*model.BlockContentDataviewFilter{
{
RelationKey: bundle.RelationKeyLayout.String(),
Condition: model.BlockContentDataviewFilter_Equal,
Value: pbtypes.Int64(int64(model.ObjectType_spaceView)),
},
{
RelationKey: bundle.RelationKeySpaceLocalStatus.String(),
Condition: model.BlockContentDataviewFilter_Equal,
Value: pbtypes.Int64(int64(model.SpaceStatus_Ok)),
},
{
RelationKey: bundle.RelationKeySpaceRemoteStatus.String(),
Condition: model.BlockContentDataviewFilter_Equal,
Value: pbtypes.Int64(int64(model.SpaceStatus_Ok)),
},
},
Sorts: []*model.BlockContentDataviewSort{
{
RelationKey: "spaceOrder",
Type: model.BlockContentDataviewSort_Asc,
NoCollate: true,
EmptyPlacement: model.BlockContentDataviewSort_End,
},
},
Keys: []string{"targetSpaceId", "name", "iconEmoji", "iconImage"},
}).Return(&pb.RpcObjectSearchResponse{
Records: []*types.Struct{
{
Fields: map[string]*types.Value{
"name": pbtypes.String("Another Workspace"),
"targetSpaceId": pbtypes.String("another-space-id"),
"iconEmoji": pbtypes.String(""),
"iconImage": pbtypes.String(iconImage),
},
},
{
Fields: map[string]*types.Value{
"name": pbtypes.String("My Workspace"),
"targetSpaceId": pbtypes.String("my-space-id"),
"iconEmoji": pbtypes.String("🚀"),
"iconImage": pbtypes.String(""),
},
},
},
Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL},
}).Once()
fx.mwMock.On("WorkspaceOpen", mock.Anything, mock.Anything).Return(&pb.RpcWorkspaceOpenResponse{
Error: &pb.RpcWorkspaceOpenResponseError{Code: pb.RpcWorkspaceOpenResponseError_NULL},
Info: &model.AccountInfo{
HomeObjectId: "home-object-id",
ArchiveObjectId: "archive-object-id",
ProfileObjectId: "profile-object-id",
MarketplaceWorkspaceId: "marketplace-workspace-id",
WorkspaceObjectId: "workspace-object-id",
DeviceId: "device-id",
AccountSpaceId: "account-space-id",
WidgetsId: "widgets-id",
SpaceViewId: "space-view-id",
TechSpaceId: "tech-space-id",
GatewayUrl: "gateway-url",
LocalStoragePath: "local-storage-path",
TimeZone: "time-zone",
AnalyticsId: "analytics-id",
NetworkId: "network-id",
},
}, nil).Twice()
// when
req, _ := http.NewRequest("GET", fmt.Sprintf("/v1/spaces?offset=%d&limit=%d", offset, limit), nil)
w := httptest.NewRecorder()
fx.router.ServeHTTP(w, req)
// then
require.Equal(t, http.StatusOK, w.Code)
var response PaginatedResponse[Space]
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
require.Len(t, response.Data, 2)
require.Equal(t, "Another Workspace", response.Data[0].Name)
require.Equal(t, "another-space-id", response.Data[0].Id)
require.Regexpf(t, regexp.MustCompile(gatewayUrl+`/image/`+iconImage), response.Data[0].Icon, "Icon URL does not match")
require.Equal(t, "My Workspace", response.Data[1].Name)
require.Equal(t, "my-space-id", response.Data[1].Id)
require.Equal(t, "🚀", response.Data[1].Icon)
})
t.Run("no spaces found", func(t *testing.T) {
// given
fx := newFixture(t)
fx.accountInfo = &model.AccountInfo{TechSpaceId: techSpaceId}
fx.mwMock.On("ObjectSearch", mock.Anything, mock.Anything).
Return(&pb.RpcObjectSearchResponse{
Records: []*types.Struct{},
Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL},
}).Once()
// when
req, _ := http.NewRequest("GET", fmt.Sprintf("/v1/spaces?offset=%d&limit=%d", offset, limit), nil)
w := httptest.NewRecorder()
fx.router.ServeHTTP(w, req)
// then
require.Equal(t, http.StatusNotFound, w.Code)
})
t.Run("failed workspace open", func(t *testing.T) {
// given
fx := newFixture(t)
fx.accountInfo = &model.AccountInfo{TechSpaceId: techSpaceId}
fx.mwMock.On("ObjectSearch", mock.Anything, mock.Anything).
Return(&pb.RpcObjectSearchResponse{
Records: []*types.Struct{
{
Fields: map[string]*types.Value{
"name": pbtypes.String("My Workspace"),
"targetSpaceId": pbtypes.String("my-space-id"),
"iconEmoji": pbtypes.String("🚀"),
"iconImage": pbtypes.String(""),
},
},
},
Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL},
}).Once()
fx.mwMock.On("WorkspaceOpen", mock.Anything, mock.Anything).
Return(&pb.RpcWorkspaceOpenResponse{
Error: &pb.RpcWorkspaceOpenResponseError{Code: pb.RpcWorkspaceOpenResponseError_UNKNOWN_ERROR},
}, nil).Once()
// when
req, _ := http.NewRequest("GET", fmt.Sprintf("/v1/spaces?offset=%d&limit=%d", offset, limit), nil)
w := httptest.NewRecorder()
fx.router.ServeHTTP(w, req)
// then
require.Equal(t, http.StatusInternalServerError, w.Code)
})
}
func TestApiServer_CreateSpaceHandler(t *testing.T) {
t.Run("successful create space", func(t *testing.T) {
// given
fx := newFixture(t)
fx.mwMock.On("WorkspaceCreate", mock.Anything, mock.Anything).
Return(&pb.RpcWorkspaceCreateResponse{
Error: &pb.RpcWorkspaceCreateResponseError{Code: pb.RpcWorkspaceCreateResponseError_NULL},
SpaceId: "new-space-id",
}).Once()
// when
body := strings.NewReader(`{"name":"New Space"}`)
req, _ := http.NewRequest("POST", "/v1/spaces", body)
w := httptest.NewRecorder()
fx.router.ServeHTTP(w, req)
// then
require.Equal(t, http.StatusOK, w.Code)
require.Contains(t, w.Body.String(), "new-space-id")
})
t.Run("invalid JSON", func(t *testing.T) {
// given
fx := newFixture(t)
// when
body := strings.NewReader(`{invalid json}`)
req, _ := http.NewRequest("POST", "/v1/spaces", body)
w := httptest.NewRecorder()
fx.router.ServeHTTP(w, req)
// then
require.Equal(t, http.StatusBadRequest, w.Code)
})
t.Run("failed workspace creation", func(t *testing.T) {
// given
fx := newFixture(t)
fx.mwMock.On("WorkspaceCreate", mock.Anything, mock.Anything).
Return(&pb.RpcWorkspaceCreateResponse{
Error: &pb.RpcWorkspaceCreateResponseError{Code: pb.RpcWorkspaceCreateResponseError_UNKNOWN_ERROR},
}).Once()
// when
body := strings.NewReader(`{"name":"Fail Space"}`)
req, _ := http.NewRequest("POST", "/v1/spaces", body)
w := httptest.NewRecorder()
fx.router.ServeHTTP(w, req)
// then
require.Equal(t, http.StatusInternalServerError, w.Code)
})
}
func TestApiServer_GetMembersHandler(t *testing.T) {
t.Run("successfully get members", func(t *testing.T) {
// given
fx := newFixture(t)
fx.accountInfo = &model.AccountInfo{TechSpaceId: techSpaceId, GatewayUrl: gatewayUrl}
fx.mwMock.On("ObjectSearch", mock.Anything, mock.Anything).
Return(&pb.RpcObjectSearchResponse{
Records: []*types.Struct{
{
Fields: map[string]*types.Value{
"id": pbtypes.String("member-1"),
"name": pbtypes.String("John Doe"),
"iconEmoji": pbtypes.String("👤"),
"identity": pbtypes.String("AAjEaEwPF4nkEh7AWkqEnzcQ8HziGB4ETjiTpvRCQvWnSMDZ"),
"globalName": pbtypes.String("john.any"),
},
},
{
Fields: map[string]*types.Value{
"id": pbtypes.String("member-2"),
"name": pbtypes.String("Jane Doe"),
"iconImage": pbtypes.String(iconImage),
"identity": pbtypes.String("AAjLbEwPF4nkEh7AWkqEnzcQ8HziGB4ETjiTpvRCQvWnSMD4"),
"globalName": pbtypes.String("jane.any"),
},
},
},
Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL},
}).Once()
// when
req, _ := http.NewRequest("GET", fmt.Sprintf("/v1/spaces/my-space/members?offset=%d&limit=%d", offset, limit), nil)
w := httptest.NewRecorder()
fx.router.ServeHTTP(w, req)
// then
require.Equal(t, http.StatusOK, w.Code)
var response PaginatedResponse[Member]
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
require.Len(t, response.Data, 2)
require.Equal(t, "member-1", response.Data[0].Id)
require.Equal(t, "John Doe", response.Data[0].Name)
require.Equal(t, "👤", response.Data[0].Icon)
require.Equal(t, "john.any", response.Data[0].GlobalName)
require.Equal(t, "member-2", response.Data[1].Id)
require.Equal(t, "Jane Doe", response.Data[1].Name)
require.Regexpf(t, regexp.MustCompile(gatewayUrl+`/image/`+iconImage), response.Data[1].Icon, "Icon URL does not match")
require.Equal(t, "jane.any", response.Data[1].GlobalName)
})
t.Run("no members found", func(t *testing.T) {
// given
fx := newFixture(t)
fx.mwMock.On("ObjectSearch", mock.Anything, mock.Anything).
Return(&pb.RpcObjectSearchResponse{
Records: []*types.Struct{},
Error: &pb.RpcObjectSearchResponseError{Code: pb.RpcObjectSearchResponseError_NULL},
}).Once()
// when
req, _ := http.NewRequest("GET", fmt.Sprintf("/v1/spaces/empty-space/members?offset=%d&limit=%d", offset, limit), nil)
w := httptest.NewRecorder()
fx.router.ServeHTTP(w, req)
// then
require.Equal(t, http.StatusNotFound, w.Code)
})
}
func TestApiServer_GetObjectsForSpaceHandler(t *testing.T) {
t.Run("successfully get objects for a space", func(t *testing.T) {
// given

View file

@ -31,7 +31,11 @@ type fixture struct {
func newFixture(t *testing.T) *fixture {
mw := mock_service.NewMockClientCommandsServer(t)
spaceService := &SpaceService{mw: mw, AccountInfo: &model.AccountInfo{TechSpaceId: techSpaceId, GatewayUrl: gatewayUrl}}
spaceService := NewService(mw)
spaceService.AccountInfo = &model.AccountInfo{
TechSpaceId: techSpaceId,
GatewayUrl: gatewayUrl,
}
return &fixture{
SpaceService: spaceService,