feat(sync): handle next cloud case for directory missing

This commit is contained in:
Johannes Millan 2025-07-18 18:56:20 +02:00
parent 27f709dbd2
commit 10cf397d4d
2 changed files with 123 additions and 6 deletions

View file

@ -213,6 +213,121 @@ describe('WebdavApi', () => {
expect(result.rev).toBe('Wed, 15 Jan 2025 10:00:00 GMT');
});
it('should add If-None-Match header when localRev is an ETag', async () => {
const mockResponse = {
status: 200,
headers: {
etag: '"newrev123"',
},
data: 'content',
};
mockHttpAdapter.request.and.returnValue(Promise.resolve(mockResponse));
mockXmlParser.validateResponseContent.and.stub();
await api.download({
path: '/test.txt',
localRev: 'abc123',
});
expect(mockHttpAdapter.request).toHaveBeenCalledWith(
jasmine.objectContaining({
headers: jasmine.objectContaining({
'If-None-Match': 'abc123',
}),
}),
);
});
it('should add If-Modified-Since header when localRev is a timestamp', async () => {
const mockResponse = {
status: 200,
headers: {
etag: '"newrev123"',
},
data: 'content',
};
mockHttpAdapter.request.and.returnValue(Promise.resolve(mockResponse));
mockXmlParser.validateResponseContent.and.stub();
const timestamp = '1642248000000'; // 2022-01-15T12:00:00.000Z
await api.download({
path: '/test.txt',
localRev: timestamp,
});
expect(mockHttpAdapter.request).toHaveBeenCalledWith(
jasmine.objectContaining({
headers: jasmine.objectContaining({
'If-Modified-Since': 'Sat, 15 Jan 2022 12:00:00 GMT',
}),
}),
);
});
it('should add If-Modified-Since header when localRev is already a date string', async () => {
const mockResponse = {
status: 200,
headers: {
etag: '"newrev123"',
},
data: 'content',
};
mockHttpAdapter.request.and.returnValue(Promise.resolve(mockResponse));
mockXmlParser.validateResponseContent.and.stub();
const dateString = 'Wed, 15 Jan 2025 10:00:00 GMT';
await api.download({
path: '/test.txt',
localRev: dateString,
});
expect(mockHttpAdapter.request).toHaveBeenCalledWith(
jasmine.objectContaining({
headers: jasmine.objectContaining({
'If-Modified-Since': dateString,
}),
}),
);
});
it('should handle 304 Not Modified response', async () => {
const mockResponse = {
status: 304,
headers: {},
data: '',
};
mockHttpAdapter.request.and.returnValue(Promise.resolve(mockResponse));
const result = await api.download({
path: '/test.txt',
localRev: 'abc123',
});
expect(result.notModified).toBe(true);
expect(result.rev).toBe('abc123');
expect(result.dataStr).toBe('');
});
it('should handle 304 response without localRev', async () => {
const mockResponse = {
status: 304,
headers: {},
data: '',
};
mockHttpAdapter.request.and.returnValue(Promise.resolve(mockResponse));
const result = await api.download({
path: '/test.txt',
localRev: null,
});
expect(result.notModified).toBe(true);
expect(result.rev).toBe('');
expect(result.dataStr).toBe('');
});
});
describe('upload', () => {

View file

@ -5,10 +5,10 @@ import { WebDavHttpAdapter, WebDavHttpResponse } from './webdav-http-adapter';
import {
HttpNotOkAPIError,
InvalidDataSPError,
RemoteFileNotFoundAPIError,
RemoteFileChangedUnexpectedly,
RemoteFileNotFoundAPIError,
} from '../../../errors/errors';
import { WebDavHttpStatus, WebDavHttpMethod, WebDavHttpHeader } from './webdav.const';
import { WebDavHttpHeader, WebDavHttpMethod, WebDavHttpStatus } from './webdav.const';
/* eslint-disable @typescript-eslint/naming-convention */
@ -207,11 +207,13 @@ export class WebdavApi {
);
}
// If we get a 409 Conflict, it might be because parent directory doesn't exist
if (
uploadError instanceof HttpNotOkAPIError &&
uploadError.response &&
uploadError.response.status === WebDavHttpStatus.CONFLICT
// if we get a 404 on upload this also indicates that the directory does not exist (for nextcloud)
uploadError instanceof RemoteFileNotFoundAPIError ||
(uploadError instanceof HttpNotOkAPIError &&
uploadError.response &&
// If we get a 409 Conflict, it might be because parent directory doesn't exist
uploadError.response.status === WebDavHttpStatus.CONFLICT)
) {
PFLog.debug(
`${WebdavApi.L}.upload() got 409, attempting to create parent directory`,