Fxied tests

server_app_share
Laurent Cozic 2021-02-04 21:13:00 +00:00
parent 439d29387f
commit a088061de9
5 changed files with 81 additions and 13 deletions

View File

@ -189,6 +189,7 @@ export default abstract class BaseModel<T> {
protected async isNew(object: T, options: SaveOptions): Promise<boolean> {
if (options.isNew === false) return false;
if (options.isNew === true) return true;
if ('id' in object && !(object as WithUuid).id) throw new Error('ID cannot be undefined or null');
return !(object as WithUuid).id;
}

View File

@ -266,8 +266,8 @@ export default class FileModel extends BaseModel<File> {
if ('name' in file && !file.is_root) {
const existingFile = await this.fileByName(parentId, file.name);
if (existingFile && options.isNew) throw new ErrorConflict(`Already a file with name "${file.name}"`);
if (existingFile && file.id === existingFile.id) throw new ErrorConflict(`Already a file with name "${file.name}"`);
if (existingFile && options.isNew) throw new ErrorConflict(`Already a file with name "${file.name}" (1)`);
if (existingFile && file.id !== existingFile.id) throw new ErrorConflict(`Already a file with name "${file.name}" (2)`);
}
if ('name' in file) {
@ -471,8 +471,30 @@ export default class FileModel extends BaseModel<File> {
const isNew = await this.isNew(object, options);
const file: File = { ... object };
let sourceFile: File = null;
if ('content' in file) file.size = file.content ? file.content.byteLength : 0;
if ('content' in file) {
let sourceFileId: string = null;
if (file.id) {
if (!('source_file_id' in file)) throw new Error('source_file_id is required when setting the content');
sourceFileId = file.source_file_id;
}
const fileSize = file.content ? file.content.byteLength : 0;
if (sourceFileId) {
sourceFile = {
id: sourceFileId,
content: file.content,
size: fileSize,
source_file_id: '',
};
delete file.content;
} else {
file.size = fileSize;
}
}
if (isNew) {
if (!file.parent_id && !file.is_root) file.parent_id = await this.userRootFileId();
@ -486,7 +508,10 @@ export default class FileModel extends BaseModel<File> {
file.owner_id = this.userId;
}
return super.save(file, options);
return this.withTransaction(async () => {
if (sourceFile) await this.save(sourceFile);
return super.save(file, options);
});
}
public async childrenCount(id: string): Promise<number> {

View File

@ -60,8 +60,23 @@ router.put('api/files/:id/content', async (path: SubPath, ctx: AppContext) => {
// https://github.com/laurent22/joplin/issues/4402
const buffer = result?.files?.file ? await fs.readFile(result.files.file.path) : Buffer.alloc(0);
const file: File = await fileModel.pathToFile(fileId, { mustExist: false, returnFullEntity: false });
file.content = buffer;
const parsedFile: File = await fileModel.pathToFile(fileId, { mustExist: false });
const isNewFile = !parsedFile.id;
const file: File = {
name: parsedFile.name,
content: buffer,
source_file_id: 'source_file_id' in parsedFile ? parsedFile.source_file_id : '',
};
if (!isNewFile) {
file.id = parsedFile.id;
} else {
file.name = parsedFile.name;
if ('parent_id' in parsedFile) file.parent_id = parsedFile.parent_id;
}
return fileModel.toApiOutput(await fileModel.save(file, { validationRules: { mustBeFile: true } }));
});
@ -70,8 +85,12 @@ router.del('api/files/:id/content', async (path: SubPath, ctx: AppContext) => {
const fileId = path.id;
const file: File = await fileModel.pathToFile(fileId, { mustExist: false, returnFullEntity: false });
if (!file) return;
file.content = Buffer.alloc(0);
await fileModel.save(file, { validationRules: { mustBeFile: true } });
await fileModel.save({
id: file.id,
content: Buffer.alloc(0),
source_file_id: file.source_file_id,
}, { validationRules: { mustBeFile: true } });
});
router.get('api/files/:id/delta', async (path: SubPath, ctx: AppContext) => {

View File

@ -44,7 +44,7 @@ describe('api_shares', function() {
test('should share a file with another user', async function() {
const { user: user1, session: session1 } = await createUserAndSession(1);
const { user: user2, session: session2 } = await createUserAndSession(2);
await createFile(user1.id, 'root:/test.txt:', 'testing share');
await createFile(user1.id, 'root:/test.txt:', 'created by sharer');
// ----------------------------------------------------------------
// Create the file share object
@ -85,7 +85,16 @@ describe('api_shares', function() {
expect(results.items[0].name).toBe('test.txt');
const fileContent = await getApi<Buffer>(session2.id, 'files/root:/test.txt:/content');
expect(fileContent.toString()).toBe('testing share');
expect(fileContent.toString()).toBe('created by sharer');
// ----------------------------------------------------------------
// If file is changed by sharee, sharer should see the change too
// ----------------------------------------------------------------
{
await updateFile(user2.id, 'root:/test.txt:', 'modified by sharee');
const fileContent = await getApi<Buffer>(session1.id, 'files/root:/test.txt:/content');
expect(fileContent.toString()).toBe('modified by sharee');
}
});
test('should get updated time of shared file', async function() {
@ -131,6 +140,10 @@ describe('api_shares', function() {
await msleep(1);
// --------------------------------------------------------------------
// If file is changed on sharer side, sharee should see the changes
// --------------------------------------------------------------------
await updateFile(user1.id, sharerFile.id, 'from sharer');
{
@ -147,8 +160,15 @@ describe('api_shares', function() {
expect(page2.items[0].item.updated_time).toBe(page1.items[0].item.updated_time);
cursor2 = page2.cursor;
}
// --------------------------------------------------------------------
// If file is changed on sharee side, sharer should see the changes
// --------------------------------------------------------------------
});
// TODO: test delta:
// - File is changed on sharer side
// - File is changed on sharee side

View File

@ -239,9 +239,12 @@ export async function createFile(userId: string, path: string, content: string):
export async function updateFile(userId: string, path: string, content: string): Promise<File> {
const fileModel = models().file({ userId });
const file: File = await fileModel.pathToFile(path, { returnFullEntity: false });
file.content = Buffer.from(content);
await fileModel.save(file);
const file: File = await fileModel.pathToFile(path, { returnFullEntity: true });
await fileModel.save({
id: file.id,
content: Buffer.from(content),
source_file_id: file.source_file_id,
});
return fileModel.load(file.id);
}