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:
parent
4c8ebaa420
commit
c49e788bc7
19 changed files with 9513 additions and 2338 deletions
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
95
core/api/apicore/mock_apicore/mock_AccountService.go
Normal file
95
core/api/apicore/mock_apicore/mock_AccountService.go
Normal 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
|
||||
}
|
96
core/api/apicore/mock_apicore/mock_ExportService.go
Normal file
96
core/api/apicore/mock_apicore/mock_ExportService.go
Normal 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
|
@ -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 object’s 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 object’s 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:
|
||||
|
|
|
@ -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 object’s 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 object’s 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})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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..."`
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
// })
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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(),
|
||||
|
|
11278
pb/commands.pb.go
11278
pb/commands.pb.go
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue