mirror of
https://github.com/standardnotes/server
synced 2026-10-05 21:13:15 -04:00
feat(syncing-server): refactor syncing to decouple getting and saving items (#659)
* feat(syncing-server): refactor syncing to decouple getting and saving items * fix(syncing-server): item hash http representation mapping * fix(syncing-server): remove redundant specs for inversify express controller
This commit is contained in:
+16
-2
@@ -10,6 +10,7 @@ import { ApiVersion } from '../../../Domain/Api/ApiVersion'
|
||||
import { SyncItems } from '../../../Domain/UseCase/Syncing/SyncItems/SyncItems'
|
||||
import { HttpStatusCode } from '@standardnotes/responses'
|
||||
import { ItemHttpRepresentation } from '../../../Mapping/Http/ItemHttpRepresentation'
|
||||
import { ItemHash } from '../../../Domain/Item/ItemHash'
|
||||
|
||||
export class HomeServerItemsController extends BaseHttpController {
|
||||
constructor(
|
||||
@@ -30,9 +31,22 @@ export class HomeServerItemsController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async sync(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
let itemHashes = []
|
||||
const itemHashes: ItemHash[] = []
|
||||
if ('items' in request.body) {
|
||||
itemHashes = request.body.items
|
||||
for (const itemHashInput of request.body.items) {
|
||||
const itemHashOrError = ItemHash.create({
|
||||
...itemHashInput,
|
||||
user_uuid: response.locals.user.uuid,
|
||||
key_system_identifier: itemHashInput.key_system_identifier ?? null,
|
||||
shared_vault_uuid: itemHashInput.shared_vault_uuid ?? null,
|
||||
})
|
||||
|
||||
if (itemHashOrError.isFailed()) {
|
||||
return this.json({ error: { message: itemHashOrError.getError() } }, HttpStatusCode.BadRequest)
|
||||
}
|
||||
|
||||
itemHashes.push(itemHashOrError.getValue())
|
||||
}
|
||||
}
|
||||
|
||||
const syncResult = await this.syncItems.execute({
|
||||
|
||||
-238
@@ -1,238 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import * as express from 'express'
|
||||
import { ContentType, MapperInterface, Result } from '@standardnotes/domain-core'
|
||||
import { results } from 'inversify-express-utils'
|
||||
|
||||
import { InversifyExpressItemsController } from './InversifyExpressItemsController'
|
||||
import { Item } from '../../Domain/Item/Item'
|
||||
import { ApiVersion } from '../../Domain/Api/ApiVersion'
|
||||
import { SyncResponse20200115 } from '../../Domain/Item/SyncResponse/SyncResponse20200115'
|
||||
import { SyncResponseFactoryInterface } from '../../Domain/Item/SyncResponse/SyncResponseFactoryInterface'
|
||||
import { SyncResponseFactoryResolverInterface } from '../../Domain/Item/SyncResponse/SyncResponseFactoryResolverInterface'
|
||||
import { CheckIntegrity } from '../../Domain/UseCase/Syncing/CheckIntegrity/CheckIntegrity'
|
||||
import { GetItem } from '../../Domain/UseCase/Syncing/GetItem/GetItem'
|
||||
import { SyncItems } from '../../Domain/UseCase/Syncing/SyncItems/SyncItems'
|
||||
import { ItemHttpRepresentation } from '../../Mapping/Http/ItemHttpRepresentation'
|
||||
|
||||
describe('InversifyExpressItemsController', () => {
|
||||
let syncItems: SyncItems
|
||||
let checkIntegrity: CheckIntegrity
|
||||
let getItem: GetItem
|
||||
let mapper: MapperInterface<Item, ItemHttpRepresentation>
|
||||
let request: express.Request
|
||||
let response: express.Response
|
||||
let syncResponceFactoryResolver: SyncResponseFactoryResolverInterface
|
||||
let syncResponseFactory: SyncResponseFactoryInterface
|
||||
let syncResponse: SyncResponse20200115
|
||||
|
||||
const createController = () =>
|
||||
new InversifyExpressItemsController(syncItems, checkIntegrity, getItem, mapper, syncResponceFactoryResolver)
|
||||
|
||||
beforeEach(() => {
|
||||
mapper = {} as jest.Mocked<MapperInterface<Item, ItemHttpRepresentation>>
|
||||
mapper.toProjection = jest.fn().mockReturnValue({ foo: 'bar' })
|
||||
|
||||
syncItems = {} as jest.Mocked<SyncItems>
|
||||
syncItems.execute = jest.fn().mockReturnValue(Result.ok({ foo: 'bar' }))
|
||||
|
||||
checkIntegrity = {} as jest.Mocked<CheckIntegrity>
|
||||
checkIntegrity.execute = jest.fn().mockReturnValue(Result.ok([{ uuid: '1-2-3', updated_at_timestamp: 2 }]))
|
||||
|
||||
getItem = {} as jest.Mocked<GetItem>
|
||||
getItem.execute = jest.fn().mockReturnValue(Result.ok({} as jest.Mocked<Item>))
|
||||
|
||||
request = {
|
||||
headers: {},
|
||||
body: {},
|
||||
params: {},
|
||||
} as jest.Mocked<express.Request>
|
||||
|
||||
request.body.api = ApiVersion.v20200115
|
||||
request.body.sync_token = 'MjoxNjE3MTk1MzQyLjc1ODEyMTc='
|
||||
request.body.limit = 150
|
||||
request.body.compute_integrity = false
|
||||
request.headers['user-agent'] = 'Google Chrome'
|
||||
request.body.items = [
|
||||
{
|
||||
content: 'test',
|
||||
content_type: ContentType.TYPES.Note,
|
||||
created_at: '2021-02-19T11:35:45.655Z',
|
||||
deleted: false,
|
||||
duplicate_of: null,
|
||||
enc_item_key: 'test',
|
||||
items_key_id: 'test',
|
||||
updated_at: '2021-02-19T11:35:45.655Z',
|
||||
uuid: '1-2-3',
|
||||
},
|
||||
]
|
||||
|
||||
response = {
|
||||
locals: {},
|
||||
} as jest.Mocked<express.Response>
|
||||
response.locals.user = {
|
||||
uuid: '123',
|
||||
}
|
||||
response.locals.freeUser = false
|
||||
|
||||
syncResponse = {} as jest.Mocked<SyncResponse20200115>
|
||||
|
||||
syncResponseFactory = {} as jest.Mocked<SyncResponseFactoryInterface>
|
||||
syncResponseFactory.createResponse = jest.fn().mockReturnValue(syncResponse)
|
||||
|
||||
syncResponceFactoryResolver = {} as jest.Mocked<SyncResponseFactoryResolverInterface>
|
||||
syncResponceFactoryResolver.resolveSyncResponseFactoryVersion = jest.fn().mockReturnValue(syncResponseFactory)
|
||||
})
|
||||
|
||||
it('should get a single item', async () => {
|
||||
request.params.uuid = '1-2-3'
|
||||
const httpResponse = <results.JsonResult>await createController().getSingleItem(request, response)
|
||||
const result = await httpResponse.executeAsync()
|
||||
|
||||
expect(getItem.execute).toHaveBeenCalledWith({
|
||||
itemUuid: '1-2-3',
|
||||
userUuid: '123',
|
||||
})
|
||||
|
||||
expect(result.statusCode).toEqual(200)
|
||||
})
|
||||
|
||||
it('should return 404 on a missing single item', async () => {
|
||||
request.params.uuid = '1-2-3'
|
||||
getItem.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||
|
||||
const httpResponse = <results.NotFoundResult>await createController().getSingleItem(request, response)
|
||||
const result = await httpResponse.executeAsync()
|
||||
|
||||
expect(getItem.execute).toHaveBeenCalledWith({
|
||||
itemUuid: '1-2-3',
|
||||
userUuid: '123',
|
||||
})
|
||||
|
||||
expect(result.statusCode).toEqual(404)
|
||||
})
|
||||
|
||||
it('should check items integrity', async () => {
|
||||
request.body.integrityPayloads = [
|
||||
{
|
||||
uuid: '1-2-3',
|
||||
updated_at_timestamp: 1,
|
||||
},
|
||||
]
|
||||
|
||||
const httpResponse = <results.JsonResult>await createController().checkItemsIntegrity(request, response)
|
||||
const result = await httpResponse.executeAsync()
|
||||
|
||||
expect(checkIntegrity.execute).toHaveBeenCalledWith({
|
||||
integrityPayloads: [
|
||||
{
|
||||
updated_at_timestamp: 1,
|
||||
uuid: '1-2-3',
|
||||
},
|
||||
],
|
||||
userUuid: '123',
|
||||
freeUser: false,
|
||||
})
|
||||
|
||||
expect(result.statusCode).toEqual(200)
|
||||
expect(await result.content.readAsStringAsync()).toEqual(
|
||||
'{"mismatches":[{"uuid":"1-2-3","updated_at_timestamp":2}]}',
|
||||
)
|
||||
})
|
||||
|
||||
it('should check items integrity with missing request parameter', async () => {
|
||||
const httpResponse = <results.JsonResult>await createController().checkItemsIntegrity(request, response)
|
||||
const result = await httpResponse.executeAsync()
|
||||
|
||||
expect(checkIntegrity.execute).toHaveBeenCalledWith({
|
||||
integrityPayloads: [],
|
||||
userUuid: '123',
|
||||
freeUser: false,
|
||||
})
|
||||
|
||||
expect(result.statusCode).toEqual(200)
|
||||
expect(await result.content.readAsStringAsync()).toEqual(
|
||||
'{"mismatches":[{"uuid":"1-2-3","updated_at_timestamp":2}]}',
|
||||
)
|
||||
})
|
||||
|
||||
it('should sync items', async () => {
|
||||
const httpResponse = <results.JsonResult>await createController().sync(request, response)
|
||||
const result = await httpResponse.executeAsync()
|
||||
|
||||
expect(syncItems.execute).toHaveBeenCalledWith({
|
||||
apiVersion: '20200115',
|
||||
computeIntegrityHash: false,
|
||||
itemHashes: [
|
||||
{
|
||||
content: 'test',
|
||||
content_type: 'Note',
|
||||
created_at: '2021-02-19T11:35:45.655Z',
|
||||
deleted: false,
|
||||
duplicate_of: null,
|
||||
enc_item_key: 'test',
|
||||
items_key_id: 'test',
|
||||
updated_at: '2021-02-19T11:35:45.655Z',
|
||||
uuid: '1-2-3',
|
||||
},
|
||||
],
|
||||
limit: 150,
|
||||
syncToken: 'MjoxNjE3MTk1MzQyLjc1ODEyMTc=',
|
||||
userUuid: '123',
|
||||
sessionUuid: null,
|
||||
})
|
||||
|
||||
expect(result.statusCode).toEqual(200)
|
||||
})
|
||||
|
||||
it('should sync items with defaulting API version if none specified', async () => {
|
||||
delete request.body.api
|
||||
|
||||
const httpResponse = <results.JsonResult>await createController().sync(request, response)
|
||||
const result = await httpResponse.executeAsync()
|
||||
|
||||
expect(syncItems.execute).toHaveBeenCalledWith({
|
||||
apiVersion: '20161215',
|
||||
computeIntegrityHash: false,
|
||||
itemHashes: [
|
||||
{
|
||||
content: 'test',
|
||||
content_type: 'Note',
|
||||
created_at: '2021-02-19T11:35:45.655Z',
|
||||
deleted: false,
|
||||
duplicate_of: null,
|
||||
enc_item_key: 'test',
|
||||
items_key_id: 'test',
|
||||
updated_at: '2021-02-19T11:35:45.655Z',
|
||||
uuid: '1-2-3',
|
||||
},
|
||||
],
|
||||
limit: 150,
|
||||
syncToken: 'MjoxNjE3MTk1MzQyLjc1ODEyMTc=',
|
||||
userUuid: '123',
|
||||
sessionUuid: null,
|
||||
})
|
||||
|
||||
expect(result.statusCode).toEqual(200)
|
||||
})
|
||||
|
||||
it('should sync items with no incoming items in request', async () => {
|
||||
response.locals.session = { uuid: '2-3-4' }
|
||||
delete request.body.items
|
||||
|
||||
const httpResponse = <results.JsonResult>await createController().sync(request, response)
|
||||
const result = await httpResponse.executeAsync()
|
||||
|
||||
expect(syncItems.execute).toHaveBeenCalledWith({
|
||||
apiVersion: '20200115',
|
||||
computeIntegrityHash: false,
|
||||
itemHashes: [],
|
||||
limit: 150,
|
||||
syncToken: 'MjoxNjE3MTk1MzQyLjc1ODEyMTc=',
|
||||
userUuid: '123',
|
||||
sessionUuid: '2-3-4',
|
||||
})
|
||||
|
||||
expect(result.statusCode).toEqual(200)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user