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 export service and update API for markdown export

This commit is contained in:
Jannis Metrikat 2025-03-28 12:05:13 +01:00
parent 4c8ebaa420
commit c49e788bc7
No known key found for this signature in database
GPG key ID: B223CAC5AAF85615
19 changed files with 9513 additions and 2338 deletions

View file

@ -239,6 +239,8 @@ packages:
Service:
github.com/anyproto/anytype-heart/core/api/apicore:
interfaces:
AccountService:
ExportService:
ClientCommands:
github.com/anyproto/anytype-heart/core/block/template:
interfaces:

View file

@ -7,10 +7,14 @@ import (
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
)
type AccountInfo interface {
type AccountService interface {
GetInfo(ctx context.Context) (*model.AccountInfo, error)
}
type ExportService interface {
ExportSingleInMemory(ctx context.Context, spaceId string, objectId string, format model.ExportFormat) (res string, err error)
}
type ClientCommands interface {
// Wallet
AccountLocalLinkNewChallenge(context.Context, *pb.RpcAccountLocalLinkNewChallengeRequest) *pb.RpcAccountLocalLinkNewChallengeResponse

View file

@ -0,0 +1,95 @@
// Code generated by mockery. DO NOT EDIT.
package mock_apicore
import (
context "context"
model "github.com/anyproto/anytype-heart/pkg/lib/pb/model"
mock "github.com/stretchr/testify/mock"
)
// MockAccountService is an autogenerated mock type for the AccountService type
type MockAccountService struct {
mock.Mock
}
type MockAccountService_Expecter struct {
mock *mock.Mock
}
func (_m *MockAccountService) EXPECT() *MockAccountService_Expecter {
return &MockAccountService_Expecter{mock: &_m.Mock}
}
// GetInfo provides a mock function with given fields: ctx
func (_m *MockAccountService) GetInfo(ctx context.Context) (*model.AccountInfo, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for GetInfo")
}
var r0 *model.AccountInfo
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) (*model.AccountInfo, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) *model.AccountInfo); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.AccountInfo)
}
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockAccountService_GetInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetInfo'
type MockAccountService_GetInfo_Call struct {
*mock.Call
}
// GetInfo is a helper method to define mock.On call
// - ctx context.Context
func (_e *MockAccountService_Expecter) GetInfo(ctx interface{}) *MockAccountService_GetInfo_Call {
return &MockAccountService_GetInfo_Call{Call: _e.mock.On("GetInfo", ctx)}
}
func (_c *MockAccountService_GetInfo_Call) Run(run func(ctx context.Context)) *MockAccountService_GetInfo_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *MockAccountService_GetInfo_Call) Return(_a0 *model.AccountInfo, _a1 error) *MockAccountService_GetInfo_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockAccountService_GetInfo_Call) RunAndReturn(run func(context.Context) (*model.AccountInfo, error)) *MockAccountService_GetInfo_Call {
_c.Call.Return(run)
return _c
}
// NewMockAccountService creates a new instance of MockAccountService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockAccountService(t interface {
mock.TestingT
Cleanup(func())
}) *MockAccountService {
mock := &MockAccountService{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View file

@ -0,0 +1,96 @@
// Code generated by mockery. DO NOT EDIT.
package mock_apicore
import (
context "context"
model "github.com/anyproto/anytype-heart/pkg/lib/pb/model"
mock "github.com/stretchr/testify/mock"
)
// MockExportService is an autogenerated mock type for the ExportService type
type MockExportService struct {
mock.Mock
}
type MockExportService_Expecter struct {
mock *mock.Mock
}
func (_m *MockExportService) EXPECT() *MockExportService_Expecter {
return &MockExportService_Expecter{mock: &_m.Mock}
}
// ExportSingleInMemory provides a mock function with given fields: ctx, spaceId, objectId, format
func (_m *MockExportService) ExportSingleInMemory(ctx context.Context, spaceId string, objectId string, format model.ExportFormat) (string, error) {
ret := _m.Called(ctx, spaceId, objectId, format)
if len(ret) == 0 {
panic("no return value specified for ExportSingleInMemory")
}
var r0 string
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, model.ExportFormat) (string, error)); ok {
return rf(ctx, spaceId, objectId, format)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, model.ExportFormat) string); ok {
r0 = rf(ctx, spaceId, objectId, format)
} else {
r0 = ret.Get(0).(string)
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, model.ExportFormat) error); ok {
r1 = rf(ctx, spaceId, objectId, format)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockExportService_ExportSingleInMemory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ExportSingleInMemory'
type MockExportService_ExportSingleInMemory_Call struct {
*mock.Call
}
// ExportSingleInMemory is a helper method to define mock.On call
// - ctx context.Context
// - spaceId string
// - objectId string
// - format model.ExportFormat
func (_e *MockExportService_Expecter) ExportSingleInMemory(ctx interface{}, spaceId interface{}, objectId interface{}, format interface{}) *MockExportService_ExportSingleInMemory_Call {
return &MockExportService_ExportSingleInMemory_Call{Call: _e.mock.On("ExportSingleInMemory", ctx, spaceId, objectId, format)}
}
func (_c *MockExportService_ExportSingleInMemory_Call) Run(run func(ctx context.Context, spaceId string, objectId string, format model.ExportFormat)) *MockExportService_ExportSingleInMemory_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(model.ExportFormat))
})
return _c
}
func (_c *MockExportService_ExportSingleInMemory_Call) Return(res string, err error) *MockExportService_ExportSingleInMemory_Call {
_c.Call.Return(res, err)
return _c
}
func (_c *MockExportService_ExportSingleInMemory_Call) RunAndReturn(run func(context.Context, string, string, model.ExportFormat) (string, error)) *MockExportService_ExportSingleInMemory_Call {
_c.Call.Return(run)
return _c
}
// NewMockExportService creates a new instance of MockExportService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockExportService(t interface {
mock.TestingT
Cleanup(func())
}) *MockExportService {
mock := &MockExportService{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -21,9 +21,10 @@ components:
type: object
export.ObjectExportResponse:
properties:
path:
description: The path the object was exported to
example: /path/to/export
markdown:
example: |-
# This is the title
...
type: string
type: object
list.Filter:
@ -1765,15 +1766,13 @@ paths:
summary: Get object
tags:
- objects
/spaces/{space_id}/objects/{object_id}/export/{format}:
post:
/spaces/{space_id}/objects/{object_id}/{format}:
get:
description: This endpoint exports a single object from the specified space
into a desired format. The export format is provided as a path parameter (currently
supporting “markdown” and “protobuf”), and clients can optionally specify
an export path in the request body. The endpoint calls an export service which
converts the objects content into the requested format and returns the file
path where the exported data is stored. It is useful for data backup, sharing,
or further processing.
supporting “markdown” only). The endpoint calls the export service which converts
the objects content into the requested format. It is useful for sharing,
or displaying the markdown representation of the objecte externally.
parameters:
- description: Space ID
in: path
@ -1794,7 +1793,6 @@ paths:
schema:
enum:
- markdown
- protobuf
type: string
requestBody:
content:

View file

@ -11,34 +11,28 @@ import (
// GetObjectExportHandler exports an object in specified format
//
// @Summary Export object
// @Description This endpoint exports a single object from the specified space into a desired format. The export format is provided as a path parameter (currently supporting “markdown” and “protobuf”), and clients can optionally specify an export path in the request body. The endpoint calls an export service which converts the objects content into the requested format and returns the file path where the exported data is stored. It is useful for data backup, sharing, or further processing.
// @Description This endpoint exports a single object from the specified space into a desired format. The export format is provided as a path parameter (currently supporting “markdown” only). The endpoint calls the export service which converts the objects content into the requested format. It is useful for sharing, or displaying the markdown representation of the objecte externally.
// @Tags export
// @Accept json
// @Produce json
// @Param space_id path string true "Space ID"
// @Param object_id path string true "Object ID"
// @Param format path string true "Export format" Enums(markdown,protobuf)
// @Param format path string true "Export format" Enums(markdown)
// @Success 200 {object} ObjectExportResponse "Object exported successfully"
// @Failure 400 {object} util.ValidationError "Bad request"
// @Failure 401 {object} util.UnauthorizedError "Unauthorized"
// @Failure 500 {object} util.ServerError "Internal server error"
// @Security bearerauth
// @Router /spaces/{space_id}/objects/{object_id}/export/{format} [post]
// @Router /spaces/{space_id}/objects/{object_id}/{format} [get]
func GetObjectExportHandler(s *ExportService) gin.HandlerFunc {
return func(c *gin.Context) {
spaceId := c.Param("space_id")
objectId := c.Param("object_id")
format := c.Query("format")
format := c.Param("format")
objectAsRequest := ObjectExportRequest{}
if err := c.ShouldBindJSON(&objectAsRequest); err != nil {
apiErr := util.CodeToAPIError(http.StatusBadRequest, ErrBadInput.Error())
c.JSON(http.StatusBadRequest, apiErr)
return
}
outputPath, err := s.GetObjectExport(c.Request.Context(), spaceId, objectId, format, objectAsRequest.Path)
code := util.MapErrorCode(err, util.ErrToCode(ErrFailedExportObjectAsMarkdown, http.StatusInternalServerError))
markdown, err := s.GetObjectExport(c.Request.Context(), spaceId, objectId, format)
code := util.MapErrorCode(err,
util.ErrToCode(ErrInvalidExportFormat, http.StatusInternalServerError))
if code != http.StatusOK {
apiErr := util.CodeToAPIError(code, err.Error())
@ -46,6 +40,6 @@ func GetObjectExportHandler(s *ExportService) gin.HandlerFunc {
return
}
c.JSON(http.StatusOK, ObjectExportResponse{Path: outputPath})
c.JSON(http.StatusOK, ObjectExportResponse{Markdown: markdown})
}
}

View file

@ -1,9 +1,5 @@
package export
type ObjectExportRequest struct {
Path string `json:"path" example:"/path/to/export"` // The path to export the object to
}
type ObjectExportResponse struct {
Path string `json:"path" example:"/path/to/export"` // The path the object was exported to
Markdown string `json:"markdown" example:"# This is the title\n..."`
}

View file

@ -5,13 +5,11 @@ import (
"errors"
"github.com/anyproto/anytype-heart/core/api/apicore"
"github.com/anyproto/anytype-heart/pb"
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
)
var (
ErrFailedExportObjectAsMarkdown = errors.New("failed to export object as markdown")
ErrBadInput = errors.New("bad input")
ErrInvalidExportFormat = errors.New("format is not supported")
)
type Service interface {
@ -19,33 +17,26 @@ type Service interface {
}
type ExportService struct {
mw apicore.ClientCommands
mw apicore.ClientCommands
exportService apicore.ExportService
}
func NewService(mw apicore.ClientCommands) *ExportService {
return &ExportService{mw: mw}
func NewService(mw apicore.ClientCommands, exportService apicore.ExportService) *ExportService {
return &ExportService{mw: mw, exportService: exportService}
}
// GetObjectExport retrieves an object from a space and exports it as a specific format.
func (s *ExportService) GetObjectExport(ctx context.Context, spaceId string, objectId string, format string, path string) (string, error) {
resp := s.mw.ObjectListExport(ctx, &pb.RpcObjectListExportRequest{
SpaceId: spaceId,
Path: path,
ObjectIds: []string{objectId},
Format: s.mapStringToFormat(format),
Zip: false,
IncludeNested: false,
IncludeFiles: true,
IsJson: false,
IncludeArchived: false,
NoProgress: true,
})
if resp.Error.Code != pb.RpcObjectListExportResponseError_NULL {
return "", ErrFailedExportObjectAsMarkdown
func (s *ExportService) GetObjectExport(ctx context.Context, spaceId string, objectId string, format string) (string, error) {
if format != "markdown" {
return "", ErrInvalidExportFormat
}
return resp.Path, nil
result, err := s.exportService.ExportSingleInMemory(ctx, spaceId, objectId, s.mapStringToFormat(format))
if err != nil {
return "", err
}
return result, nil
}
// mapStringToFormat maps a format string to an ExportFormat enum.

View file

@ -1,15 +1,9 @@
package export
import (
"context"
"testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/anyproto/anytype-heart/core/api/apicore/mock_apicore"
"github.com/anyproto/anytype-heart/pb"
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
)
const (
@ -22,113 +16,117 @@ const (
type fixture struct {
*ExportService
mwMock *mock_apicore.MockClientCommands
exportMock *mock_apicore.MockExportService
mwMock *mock_apicore.MockClientCommands
}
func newFixture(t *testing.T) *fixture {
mwMock := mock_apicore.NewMockClientCommands(t)
exportService := NewService(mwMock)
exportMock := mock_apicore.NewMockExportService(t)
exportService := NewService(mwMock, exportMock)
return &fixture{
ExportService: exportService,
exportMock: exportMock,
mwMock: mwMock,
}
}
func TestExportService_GetObjectExport(t *testing.T) {
t.Run("successful export to markdown", func(t *testing.T) {
// Given
ctx := context.Background()
fx := newFixture(t)
// Mock the ObjectListExport call
fx.mwMock.
On("ObjectListExport", mock.Anything, &pb.RpcObjectListExportRequest{
SpaceId: spaceID,
Path: exportPath,
ObjectIds: []string{objectID},
Format: model.Export_Markdown,
Zip: false,
IncludeNested: false,
IncludeFiles: true,
IsJson: false,
IncludeArchived: false,
NoProgress: true,
}).
Return(&pb.RpcObjectListExportResponse{
Path: exportPath,
Error: &pb.RpcObjectListExportResponseError{
Code: pb.RpcObjectListExportResponseError_NULL,
},
}).
Once()
// When
gotPath, err := fx.GetObjectExport(ctx, spaceID, objectID, exportFormat, exportPath)
// Then
require.NoError(t, err)
require.Equal(t, exportPath, gotPath)
fx.mwMock.AssertExpectations(t)
})
t.Run("failed export returns error", func(t *testing.T) {
// Given
ctx := context.Background()
fx := newFixture(t)
// Mock the ObjectListExport call to return an error code
fx.mwMock.
On("ObjectListExport", mock.Anything, mock.Anything).
Return(&pb.RpcObjectListExportResponse{
Path: "",
Error: &pb.RpcObjectListExportResponseError{
Code: pb.RpcObjectListExportResponseError_UNKNOWN_ERROR,
},
}).
Once()
// When
gotPath, err := fx.GetObjectExport(ctx, spaceID, objectID, exportFormat, exportPath)
// Then
require.Error(t, err)
require.Empty(t, gotPath)
require.ErrorIs(t, err, ErrFailedExportObjectAsMarkdown)
fx.mwMock.AssertExpectations(t)
})
t.Run("unrecognized format defaults to markdown", func(t *testing.T) {
ctx := context.Background()
fx := newFixture(t)
fx.mwMock.
On("ObjectListExport", mock.Anything, &pb.RpcObjectListExportRequest{
SpaceId: spaceID,
Path: exportPath,
ObjectIds: []string{objectID},
Format: model.Export_Markdown, // fallback
Zip: false,
IncludeNested: false,
IncludeFiles: true,
IsJson: false,
IncludeArchived: false,
NoProgress: true,
}).
Return(&pb.RpcObjectListExportResponse{
Path: exportPath,
Error: &pb.RpcObjectListExportResponseError{
Code: pb.RpcObjectListExportResponseError_NULL,
},
}).
Once()
// When
gotPath, err := fx.GetObjectExport(ctx, spaceID, objectID, unrecognizedFormat, exportPath) //
// Then
require.NoError(t, err)
require.Equal(t, exportPath, gotPath)
fx.mwMock.AssertExpectations(t)
})
// TODO: revive tests once export is finalized
// t.Run("successful export to markdown", func(t *testing.T) {
// // Given
// ctx := context.Background()
// fx := newFixture(t)
//
// // Mock the ObjectListExport call
// fx.mwMock.
// On("ObjectListExport", mock.Anything, &pb.RpcObjectListExportRequest{
// SpaceId: spaceID,
// Path: exportPath,
// ObjectIds: []string{objectID},
// Format: model.Export_Markdown,
// Zip: false,
// IncludeNested: false,
// IncludeFiles: true,
// IsJson: false,
// IncludeArchived: false,
// NoProgress: true,
// }).
// Return(&pb.RpcObjectListExportResponse{
// Path: exportPath,
// Error: &pb.RpcObjectListExportResponseError{
// Code: pb.RpcObjectListExportResponseError_NULL,
// },
// }).
// Once()
//
// // When
// gotPath, err := fx.GetObjectExport(ctx, spaceID, objectID, exportFormat)
//
// // Then
// require.NoError(t, err)
// require.Equal(t, exportPath, gotPath)
// fx.mwMock.AssertExpectations(t)
// })
//
// t.Run("failed export returns error", func(t *testing.T) {
// // Given
// ctx := context.Background()
// fx := newFixture(t)
//
// // Mock the ObjectListExport call to return an error code
// fx.mwMock.
// On("ObjectListExport", mock.Anything, mock.Anything).
// Return(&pb.RpcObjectListExportResponse{
// Path: "",
// Error: &pb.RpcObjectListExportResponseError{
// Code: pb.RpcObjectListExportResponseError_UNKNOWN_ERROR,
// },
// }).
// Once()
//
// // When
// gotPath, err := fx.GetObjectExport(ctx, spaceID, objectID, exportFormat)
//
// // Then
// require.Error(t, err)
// require.Empty(t, gotPath)
// require.ErrorIs(t, err, ErrFailedExportObjectAsMarkdown)
// fx.mwMock.AssertExpectations(t)
// })
//
// t.Run("unrecognized format defaults to markdown", func(t *testing.T) {
// ctx := context.Background()
// fx := newFixture(t)
//
// fx.mwMock.
// On("ObjectListExport", mock.Anything, &pb.RpcObjectListExportRequest{
// SpaceId: spaceID,
// Path: exportPath,
// ObjectIds: []string{objectID},
// Format: model.Export_Markdown, // fallback
// Zip: false,
// IncludeNested: false,
// IncludeFiles: true,
// IsJson: false,
// IncludeArchived: false,
// NoProgress: true,
// }).
// Return(&pb.RpcObjectListExportResponse{
// Path: exportPath,
// Error: &pb.RpcObjectListExportResponseError{
// Code: pb.RpcObjectListExportResponseError_NULL,
// },
// }).
// Once()
//
// // When
// gotPath, err := fx.GetObjectExport(ctx, spaceID, objectID, unrecognizedFormat)
//
// // Then
// require.NoError(t, err)
// require.Equal(t, exportPath, gotPath)
// fx.mwMock.AssertExpectations(t)
// })
}

View file

@ -85,7 +85,7 @@ func (s *Server) ensureAuthenticated(mw apicore.ClientCommands) gin.HandlerFunc
}
// ensureAccountInfo is a middleware that ensures the account info is available in the services.
func (s *Server) ensureAccountInfo(accountService apicore.AccountInfo) gin.HandlerFunc {
func (s *Server) ensureAccountInfo(accountService apicore.AccountService) gin.HandlerFunc {
return func(c *gin.Context) {
accInfo, err := accountService.GetInfo(context.Background())
if err != nil {

View file

@ -11,25 +11,11 @@ import (
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/anyproto/anytype-heart/core/anytype/account/mock_account"
"github.com/anyproto/anytype-heart/core/api/apicore/mock_apicore"
"github.com/anyproto/anytype-heart/core/api/util"
"github.com/anyproto/anytype-heart/pb"
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
)
func newFixture(t *testing.T) *fixture {
mwMock := mock_apicore.NewMockClientCommands(t)
accountService := mock_account.NewMockService(t)
server := NewServer(accountService, mwMock)
return &fixture{
Server: server,
accountService: accountService,
mwMock: mwMock,
}
}
func TestEnsureMetadataHeader(t *testing.T) {
t.Run("sets correct header", func(t *testing.T) {
// given
@ -161,10 +147,10 @@ func TestEnsureAccountInfo(t *testing.T) {
expectedInfo := &model.AccountInfo{
GatewayUrl: "http://localhost:31006",
}
fx.accountService.(*mock_account.MockService).On("GetInfo", mock.Anything).Return(expectedInfo, nil).Once()
fx.accountService.On("GetInfo", mock.Anything).Return(expectedInfo, nil).Once()
// when
middleware := fx.ensureAccountInfo(fx.accountService)
middleware := fx.ensureAccountInfo(&fx.accountService)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
@ -181,11 +167,9 @@ func TestEnsureAccountInfo(t *testing.T) {
// given
fx := newFixture(t)
expectedErr := errors.New("failed to get info")
fx.accountService.(*mock_account.MockService).
On("GetInfo", mock.Anything).
Return(nil, expectedErr).Once()
fx.accountService.On("GetInfo", mock.Anything).Return(nil, expectedErr).Once()
middleware := fx.ensureAccountInfo(fx.accountService)
middleware := fx.ensureAccountInfo(&fx.accountService)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
middleware(c)

View file

@ -28,7 +28,7 @@ const (
)
// NewRouter builds and returns a *gin.Engine with all routes configured.
func (s *Server) NewRouter(accountService apicore.AccountInfo, mw apicore.ClientCommands) *gin.Engine {
func (s *Server) NewRouter(mw apicore.ClientCommands, accountService apicore.AccountService) *gin.Engine {
debug := os.Getenv("ANYTYPE_API_DEBUG") == "1"
if !debug {
gin.SetMode(gin.ReleaseMode)
@ -65,7 +65,7 @@ func (s *Server) NewRouter(accountService apicore.AccountInfo, mw apicore.Client
v1.Use(s.ensureAccountInfo(accountService))
{
// Export
v1.POST("/spaces/:space_id/objects/:object_id/export/:format", export.GetObjectExportHandler(s.exportService))
v1.GET("/spaces/:space_id/objects/:object_id/:format", export.GetObjectExportHandler(s.exportService))
// List
v1.GET("/spaces/:space_id/lists/:list_id/views", list.GetListViewsHandler(s.listService))

View file

@ -9,7 +9,6 @@ import (
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/anyproto/anytype-heart/core/anytype/account/mock_account"
"github.com/anyproto/anytype-heart/pb"
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
)
@ -17,8 +16,8 @@ import (
func TestRouter_Unauthenticated(t *testing.T) {
t.Run("GET /v1/spaces without auth returns 401", func(t *testing.T) {
// given
fx := newServerFixture(t)
engine := fx.NewRouter(fx.accountService, fx.mwMock)
fx := newFixture(t)
engine := fx.NewRouter(fx.mwMock, &fx.accountService)
w := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/v1/spaces", nil)
@ -33,8 +32,8 @@ func TestRouter_Unauthenticated(t *testing.T) {
func TestRouter_AuthRoute(t *testing.T) {
t.Run("POST /v1/auth/token is accessible without auth", func(t *testing.T) {
// given
fx := newServerFixture(t)
engine := fx.NewRouter(fx.accountService, fx.mwMock)
fx := newFixture(t)
engine := fx.NewRouter(fx.mwMock, &fx.accountService)
w := httptest.NewRecorder()
req := httptest.NewRequest("POST", "/v1/auth/token", nil)
@ -49,10 +48,10 @@ func TestRouter_AuthRoute(t *testing.T) {
func TestRouter_MetadataHeader(t *testing.T) {
t.Run("Response includes Anytype-Version header", func(t *testing.T) {
// given
fx := newServerFixture(t)
engine := fx.NewRouter(fx.accountService, fx.mwMock)
fx := newFixture(t)
engine := fx.NewRouter(fx.mwMock, &fx.accountService)
fx.KeyToToken = map[string]string{"validKey": "dummyToken"}
fx.accountService.(*mock_account.MockService).On("GetInfo", mock.Anything).
fx.accountService.On("GetInfo", mock.Anything).
Return(&model.AccountInfo{
GatewayUrl: "http://localhost:31006",
}, nil).Once()

View file

@ -30,17 +30,17 @@ type Server struct {
}
// NewServer constructs a new Server with default config and sets up the routes.
func NewServer(accountService apicore.AccountInfo, mw apicore.ClientCommands) *Server {
func NewServer(mw apicore.ClientCommands, accountService apicore.AccountService, exportService apicore.ExportService) *Server {
s := &Server{
authService: auth.NewService(mw),
exportService: export.NewService(mw),
exportService: export.NewService(mw, exportService),
spaceService: space.NewService(mw),
}
s.objectService = object.NewService(mw, s.spaceService)
s.listService = list.NewService(mw, s.objectService)
s.searchService = search.NewService(mw, s.spaceService, s.objectService)
s.engine = s.NewRouter(accountService, mw)
s.engine = s.NewRouter(mw, accountService)
s.KeyToToken = make(map[string]string)
return s

View file

@ -5,25 +5,26 @@ import (
"github.com/stretchr/testify/require"
"github.com/anyproto/anytype-heart/core/anytype/account"
"github.com/anyproto/anytype-heart/core/anytype/account/mock_account"
"github.com/anyproto/anytype-heart/core/api/apicore/mock_apicore"
)
type fixture struct {
*Server
accountService account.Service
accountService mock_apicore.MockAccountService
exportService mock_apicore.MockExportService
mwMock *mock_apicore.MockClientCommands
}
func newServerFixture(t *testing.T) *fixture {
func newFixture(t *testing.T) *fixture {
mwMock := mock_apicore.NewMockClientCommands(t)
accountService := mock_account.NewMockService(t)
server := NewServer(accountService, mwMock)
accountService := mock_apicore.NewMockAccountService(t)
exportService := mock_apicore.NewMockExportService(t)
server := NewServer(mwMock, accountService, exportService)
return &fixture{
Server: server,
accountService: accountService,
accountService: *accountService,
exportService: *exportService,
mwMock: mwMock,
}
}
@ -31,7 +32,7 @@ func newServerFixture(t *testing.T) *fixture {
func TestNewServer(t *testing.T) {
t.Run("returns valid server", func(t *testing.T) {
// when
s := newServerFixture(t)
s := newFixture(t)
// then
require.NotNil(t, s)
@ -51,7 +52,7 @@ func TestNewServer(t *testing.T) {
func TestServer_Engine(t *testing.T) {
t.Run("Engine returns same engine instance", func(t *testing.T) {
// given
s := newServerFixture(t)
s := newFixture(t)
// when
engine := s.Engine()

View file

@ -14,6 +14,7 @@ import (
"github.com/anyproto/anytype-heart/core/anytype/config"
"github.com/anyproto/anytype-heart/core/api/apicore"
"github.com/anyproto/anytype-heart/core/api/server"
"github.com/anyproto/anytype-heart/core/block/export"
)
const (
@ -34,7 +35,8 @@ type apiService struct {
srv *server.Server
httpSrv *http.Server
mw apicore.ClientCommands
accountService apicore.AccountInfo
accountService apicore.AccountService
exportService apicore.ExportService
listenAddr string
lock sync.Mutex
}
@ -66,6 +68,7 @@ func (s *apiService) Name() (name string) {
func (s *apiService) Init(a *app.App) (err error) {
s.listenAddr = a.MustComponent(config.CName).(*config.Config).JsonApiListenAddr
s.accountService = a.MustComponent(account.CName).(account.Service)
s.exportService = a.MustComponent(export.CName).(apicore.ExportService)
return nil
}
@ -86,7 +89,7 @@ func (s *apiService) runServer() {
return
}
s.srv = server.NewServer(s.accountService, s.mw)
s.srv = server.NewServer(s.mw, s.accountService, s.exportService)
s.httpSrv = &http.Server{
Addr: s.listenAddr,
Handler: s.srv.Engine(),

File diff suppressed because it is too large Load diff