diff --git a/src/app/pfapi/api/sync/providers/webdav/webdav-api.spec.ts b/src/app/pfapi/api/sync/providers/webdav/webdav-api.spec.ts index 19f133cb36..9ea93e0462 100644 --- a/src/app/pfapi/api/sync/providers/webdav/webdav-api.spec.ts +++ b/src/app/pfapi/api/sync/providers/webdav/webdav-api.spec.ts @@ -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', () => { diff --git a/src/app/pfapi/api/sync/providers/webdav/webdav-api.ts b/src/app/pfapi/api/sync/providers/webdav/webdav-api.ts index 0a5d05877e..daa5eac646 100644 --- a/src/app/pfapi/api/sync/providers/webdav/webdav-api.ts +++ b/src/app/pfapi/api/sync/providers/webdav/webdav-api.ts @@ -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`,