Remote sync of webdav

This commit is contained in:
Courtney Arnold 2026-08-13 17:05:10 -05:00
parent f01186df79
commit 483fbeafb6
44 changed files with 4842 additions and 975 deletions

View file

@ -0,0 +1,122 @@
import 'package:fuel_tax_tracker/services/cloud/webdav_provider.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
final baseUrl = Uri.parse('https://cloud.example.com/');
group('parseWebDavMultistatus', () {
// Real WebDAV servers disagree on namespace prefix for the same "DAV:"
// namespace — Nextcloud defaults to `d:`, many Apache mod_dav setups
// use `D:`, and some use no prefix at all with a default xmlns. All
// three must parse identically since the parser matches by local name.
test('parses a lowercase d: prefixed response (Nextcloud-style)', () {
final xml = '''<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:">
<d:response>
<d:href>/remote.php/dav/files/user/MO-Fuel-Tax-Back/</d:href>
<d:propstat>
<d:prop>
<d:resourcetype><d:collection/></d:resourcetype>
<d:getetag>&quot;abc123&quot;</d:getetag>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<d:response>
<d:href>/remote.php/dav/files/user/MO-Fuel-Tax-Back/receipts/</d:href>
<d:propstat>
<d:prop>
<d:resourcetype><d:collection/></d:resourcetype>
<d:getetag>&quot;def456&quot;</d:getetag>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<d:response>
<d:href>/remote.php/dav/files/user/MO-Fuel-Tax-Back/fuel_tax_tracker.db</d:href>
<d:propstat>
<d:prop>
<d:resourcetype/>
<d:getetag>&quot;ghi789&quot;</d:getetag>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>''';
final entries = parseWebDavMultistatus(xml, baseUrl);
expect(entries, hasLength(3));
expect(entries[0].path, '/remote.php/dav/files/user/MO-Fuel-Tax-Back/');
expect(entries[0].isCollection, isTrue);
expect(entries[0].etag, '"abc123"');
expect(entries[1].isCollection, isTrue);
expect(entries[2].path, endsWith('fuel_tax_tracker.db'));
expect(entries[2].isCollection, isFalse);
expect(entries[2].etag, '"ghi789"');
});
test('parses an uppercase D: prefixed response identically', () {
final xml = '''<?xml version="1.0"?>
<D:multistatus xmlns:D="DAV:">
<D:response>
<D:href>/dav/MO-Fuel-Tax-Back/</D:href>
<D:propstat>
<D:prop>
<D:resourcetype><D:collection/></D:resourcetype>
<D:getetag>"xyz111"</D:getetag>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
</D:multistatus>''';
final entries = parseWebDavMultistatus(xml, baseUrl);
expect(entries, hasLength(1));
expect(entries.first.isCollection, isTrue);
expect(entries.first.etag, '"xyz111"');
});
test('parses an unprefixed default-namespace response identically', () {
final xml = '''<?xml version="1.0"?>
<multistatus xmlns="DAV:">
<response>
<href>/dav/MO-Fuel-Tax-Back/lock-file.lock</href>
<propstat>
<prop>
<resourcetype/>
<getetag>"lock999"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
</multistatus>''';
final entries = parseWebDavMultistatus(xml, baseUrl);
expect(entries, hasLength(1));
expect(entries.first.isCollection, isFalse);
expect(entries.first.path, '/dav/MO-Fuel-Tax-Back/lock-file.lock');
});
test('a file with no getetag prop yields a null etag', () {
final xml = '''<?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:">
<d:response>
<d:href>/dav/no-etag.txt</d:href>
<d:propstat>
<d:prop>
<d:resourcetype/>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>''';
final entries = parseWebDavMultistatus(xml, baseUrl);
expect(entries.single.etag, isNull);
});
});
}