diff --git a/packages/nextcloud/test/core_test.dart b/packages/nextcloud/test/core_test.dart index 2e2b211e..3e99b608 100644 --- a/packages/nextcloud/test/core_test.dart +++ b/packages/nextcloud/test/core_test.dart @@ -1,160 +1,162 @@ -@Retry(3) -library core_test; - import 'package:nextcloud/nextcloud.dart'; import 'package:test/test.dart'; import 'helper.dart'; void main() { - group('core', () { - late DockerImage image; - setUpAll(() async => image = await getDockerImage()); - - late DockerContainer container; - late TestNextcloudClient client; - setUp(() async { - container = await getDockerContainer(image); - client = await getTestClient(container); - }); - tearDown(() => container.destroy()); - - test('Is supported from capabilities', () async { - final (supported, _) = client.core.isSupported((await client.core.ocs.getCapabilities()).ocs.data); - expect(supported, isTrue); - }); - - test('Is supported from status', () async { - final status = await client.core.getStatus(); - expect(status.isSupported, isTrue); - }); - - test('Get status', () async { - final status = await client.core.getStatus(); - expect(status.installed, true); - expect(status.maintenance, false); - expect(status.needsDbUpgrade, false); - expect(status.version, startsWith('$coreSupportedVersion.')); - expect(status.versionstring, startsWith('$coreSupportedVersion.')); - expect(status.edition, ''); - expect(status.productname, 'Nextcloud'); - expect(status.extendedSupport, false); - }); - - test('Get capabilities', () async { - final capabilities = await client.core.ocs.getCapabilities(); - expect(capabilities.ocs.data.version.major, coreSupportedVersion); - expect(capabilities.ocs.data.version.string, startsWith('$coreSupportedVersion.')); - expect(capabilities.ocs.data.capabilities.commentsCapabilities, isNotNull); - expect(capabilities.ocs.data.capabilities.davCapabilities, isNotNull); - expect(capabilities.ocs.data.capabilities.filesCapabilities, isNotNull); - expect(capabilities.ocs.data.capabilities.filesSharingCapabilities, isNotNull); - expect(capabilities.ocs.data.capabilities.filesTrashbinCapabilities, isNotNull); - expect(capabilities.ocs.data.capabilities.filesVersionsCapabilities, isNotNull); - expect(capabilities.ocs.data.capabilities.notesCapabilities, isNotNull); - expect(capabilities.ocs.data.capabilities.notificationsCapabilities, isNotNull); - expect(capabilities.ocs.data.capabilities.provisioningApiCapabilities, isNotNull); - expect(capabilities.ocs.data.capabilities.sharebymailCapabilities, isNotNull); - expect(capabilities.ocs.data.capabilities.themingPublicCapabilities, isNotNull); - expect(capabilities.ocs.data.capabilities.userStatusCapabilities, isNotNull); - expect(capabilities.ocs.data.capabilities.weatherStatusCapabilities, isNotNull); - }); - - test('Get navigation apps', () async { - final navigationApps = await client.core.navigation.getAppsNavigation(); - expect(navigationApps.ocs.data, hasLength(6)); - expect(navigationApps.ocs.data[0].id, 'dashboard'); - expect(navigationApps.ocs.data[1].id, 'files'); - expect(navigationApps.ocs.data[2].id, 'photos'); - expect(navigationApps.ocs.data[3].id, 'activity'); - expect(navigationApps.ocs.data[4].id, 'notes'); - expect(navigationApps.ocs.data[5].id, 'news'); - }); - - test( - 'Autocomplete', - () async { - final response = await client.core.autoComplete.$get( - search: '', - itemType: 'call', - itemId: 'new', - shareTypes: [ - ShareType.user.index, - ShareType.group.index, - ], - ); - expect(response.ocs.data, hasLength(3)); - - expect(response.ocs.data[0].id, 'admin'); - expect(response.ocs.data[0].label, 'admin'); - expect(response.ocs.data[0].icon, 'icon-user'); - expect(response.ocs.data[0].source, 'users'); - expect(response.ocs.data[0].status, isEmpty); - expect(response.ocs.data[0].subline, ''); - expect(response.ocs.data[0].shareWithDisplayNameUnique, 'admin@example.com'); - - expect(response.ocs.data[1].id, 'user2'); - expect(response.ocs.data[1].label, 'User Two'); - expect(response.ocs.data[1].icon, 'icon-user'); - expect(response.ocs.data[1].source, 'users'); - expect(response.ocs.data[1].status, isEmpty); - expect(response.ocs.data[1].subline, ''); - expect(response.ocs.data[1].shareWithDisplayNameUnique, 'user2'); - - expect(response.ocs.data[2].id, 'admin'); - expect(response.ocs.data[2].label, 'admin'); - expect(response.ocs.data[2].icon, ''); - expect(response.ocs.data[2].source, 'groups'); - expect(response.ocs.data[2].status, isEmpty); - expect(response.ocs.data[2].subline, ''); - expect(response.ocs.data[2].shareWithDisplayNameUnique, ''); - }, - skip: true, // TODO: This test only works on 28+ due to a bug fix with the status - ); - - test('Get preview', () async { - final response = await client.core.preview.getPreview(file: 'Nextcloud.png'); - expect(response, isNotEmpty); - }); - - test('Get avatar', () async { - final response = await client.core.avatar.getAvatar(userId: 'admin', size: 32); - expect(response.data, isNotEmpty); - }); - - test('Get dark avatar', () async { - final response = await client.core.avatar.getAvatarDark(userId: 'admin', size: 32); - expect(response.data, isNotEmpty); - }); - - test('Delete app password', () async { - await client.core.appPassword.deleteAppPassword(); - expect( - () => client.core.appPassword.deleteAppPassword(), - throwsA(predicate((final e) => (e! as DynamiteApiException).statusCode == 401)), + group( + 'core', + () { + late DockerImage image; + setUpAll(() async => image = await getDockerImage()); + + late DockerContainer container; + late TestNextcloudClient client; + setUp(() async { + container = await getDockerContainer(image); + client = await getTestClient(container); + }); + tearDown(() => container.destroy()); + + test('Is supported from capabilities', () async { + final (supported, _) = client.core.isSupported((await client.core.ocs.getCapabilities()).ocs.data); + expect(supported, isTrue); + }); + + test('Is supported from status', () async { + final status = await client.core.getStatus(); + expect(status.isSupported, isTrue); + }); + + test('Get status', () async { + final status = await client.core.getStatus(); + expect(status.installed, true); + expect(status.maintenance, false); + expect(status.needsDbUpgrade, false); + expect(status.version, startsWith('$coreSupportedVersion.')); + expect(status.versionstring, startsWith('$coreSupportedVersion.')); + expect(status.edition, ''); + expect(status.productname, 'Nextcloud'); + expect(status.extendedSupport, false); + }); + + test('Get capabilities', () async { + final capabilities = await client.core.ocs.getCapabilities(); + expect(capabilities.ocs.data.version.major, coreSupportedVersion); + expect(capabilities.ocs.data.version.string, startsWith('$coreSupportedVersion.')); + expect(capabilities.ocs.data.capabilities.commentsCapabilities, isNotNull); + expect(capabilities.ocs.data.capabilities.davCapabilities, isNotNull); + expect(capabilities.ocs.data.capabilities.filesCapabilities, isNotNull); + expect(capabilities.ocs.data.capabilities.filesSharingCapabilities, isNotNull); + expect(capabilities.ocs.data.capabilities.filesTrashbinCapabilities, isNotNull); + expect(capabilities.ocs.data.capabilities.filesVersionsCapabilities, isNotNull); + expect(capabilities.ocs.data.capabilities.notesCapabilities, isNotNull); + expect(capabilities.ocs.data.capabilities.notificationsCapabilities, isNotNull); + expect(capabilities.ocs.data.capabilities.provisioningApiCapabilities, isNotNull); + expect(capabilities.ocs.data.capabilities.sharebymailCapabilities, isNotNull); + expect(capabilities.ocs.data.capabilities.themingPublicCapabilities, isNotNull); + expect(capabilities.ocs.data.capabilities.userStatusCapabilities, isNotNull); + expect(capabilities.ocs.data.capabilities.weatherStatusCapabilities, isNotNull); + }); + + test('Get navigation apps', () async { + final navigationApps = await client.core.navigation.getAppsNavigation(); + expect(navigationApps.ocs.data, hasLength(6)); + expect(navigationApps.ocs.data[0].id, 'dashboard'); + expect(navigationApps.ocs.data[1].id, 'files'); + expect(navigationApps.ocs.data[2].id, 'photos'); + expect(navigationApps.ocs.data[3].id, 'activity'); + expect(navigationApps.ocs.data[4].id, 'notes'); + expect(navigationApps.ocs.data[5].id, 'news'); + }); + + test( + 'Autocomplete', + () async { + final response = await client.core.autoComplete.$get( + search: '', + itemType: 'call', + itemId: 'new', + shareTypes: [ + ShareType.user.index, + ShareType.group.index, + ], + ); + expect(response.ocs.data, hasLength(3)); + + expect(response.ocs.data[0].id, 'admin'); + expect(response.ocs.data[0].label, 'admin'); + expect(response.ocs.data[0].icon, 'icon-user'); + expect(response.ocs.data[0].source, 'users'); + expect(response.ocs.data[0].status, isEmpty); + expect(response.ocs.data[0].subline, ''); + expect(response.ocs.data[0].shareWithDisplayNameUnique, 'admin@example.com'); + + expect(response.ocs.data[1].id, 'user2'); + expect(response.ocs.data[1].label, 'User Two'); + expect(response.ocs.data[1].icon, 'icon-user'); + expect(response.ocs.data[1].source, 'users'); + expect(response.ocs.data[1].status, isEmpty); + expect(response.ocs.data[1].subline, ''); + expect(response.ocs.data[1].shareWithDisplayNameUnique, 'user2'); + + expect(response.ocs.data[2].id, 'admin'); + expect(response.ocs.data[2].label, 'admin'); + expect(response.ocs.data[2].icon, ''); + expect(response.ocs.data[2].source, 'groups'); + expect(response.ocs.data[2].status, isEmpty); + expect(response.ocs.data[2].subline, ''); + expect(response.ocs.data[2].shareWithDisplayNameUnique, ''); + }, + skip: true, // TODO: This test only works on 28+ due to a bug fix with the status ); - }); - test('Unified search providers', () async { - final response = await client.core.unifiedSearch.getProviders(); - expect(response.ocs.data, hasLength(13)); - }); + test('Get preview', () async { + final response = await client.core.preview.getPreview(file: 'Nextcloud.png'); + expect(response, isNotEmpty); + }); + + test('Get avatar', () async { + final response = await client.core.avatar.getAvatar(userId: 'admin', size: 32); + expect(response.data, isNotEmpty); + }); + + test('Get dark avatar', () async { + final response = await client.core.avatar.getAvatarDark(userId: 'admin', size: 32); + expect(response.data, isNotEmpty); + }); + + test('Delete app password', () async { + await client.core.appPassword.deleteAppPassword(); + expect( + () => client.core.appPassword.deleteAppPassword(), + throwsA(predicate((final e) => (e! as DynamiteApiException).statusCode == 401)), + ); + }); + + test('Unified search providers', () async { + final response = await client.core.unifiedSearch.getProviders(); + expect(response.ocs.data, hasLength(13)); + }); - test('Unified search', () async { - final response = await client.core.unifiedSearch.search( - providerId: 'settings', - term: 'Personal info', - ); - expect(response.ocs.data.name, 'Settings'); - expect(response.ocs.data.isPaginated, isFalse); - expect(response.ocs.data.entries, hasLength(1)); - expect(response.ocs.data.entries.single.thumbnailUrl, isEmpty); - expect(response.ocs.data.entries.single.title, 'Personal info'); - expect(response.ocs.data.entries.single.subline, isEmpty); - expect(response.ocs.data.entries.single.resourceUrl, isNotEmpty); - expect(response.ocs.data.entries.single.icon, 'icon-settings-dark'); - expect(response.ocs.data.entries.single.rounded, isFalse); - expect(response.ocs.data.entries.single.attributes, isEmpty); - }); - }); + test('Unified search', () async { + final response = await client.core.unifiedSearch.search( + providerId: 'settings', + term: 'Personal info', + ); + expect(response.ocs.data.name, 'Settings'); + expect(response.ocs.data.isPaginated, isFalse); + expect(response.ocs.data.entries, hasLength(1)); + expect(response.ocs.data.entries.single.thumbnailUrl, isEmpty); + expect(response.ocs.data.entries.single.title, 'Personal info'); + expect(response.ocs.data.entries.single.subline, isEmpty); + expect(response.ocs.data.entries.single.resourceUrl, isNotEmpty); + expect(response.ocs.data.entries.single.icon, 'icon-settings-dark'); + expect(response.ocs.data.entries.single.rounded, isFalse); + expect(response.ocs.data.entries.single.attributes, isEmpty); + }); + }, + retry: retryCount, + timeout: timeout, + ); } diff --git a/packages/nextcloud/test/helper.dart b/packages/nextcloud/test/helper.dart index 9f796768..689f907c 100644 --- a/packages/nextcloud/test/helper.dart +++ b/packages/nextcloud/test/helper.dart @@ -7,6 +7,9 @@ import 'package:nextcloud/nextcloud.dart'; import 'package:process_run/cmd_run.dart'; import 'package:test/test.dart'; +const retryCount = 3; +const timeout = Timeout(Duration(seconds: 30)); + class DockerContainer { DockerContainer({ required this.id, @@ -131,12 +134,18 @@ Future getTestClient( cookieJar: CookieJar(), ); + var i = 0; while (true) { - // Test will timeout after 30s try { await client.core.getStatus(); break; - } catch (_) {} + } on CoreApiException catch (error) { + i++; + await Future.delayed(const Duration(milliseconds: 100)); + if (i >= 30) { + throw TimeoutException('Failed to get the status of the Server. $error'); + } + } } return client; diff --git a/packages/nextcloud/test/news_test.dart b/packages/nextcloud/test/news_test.dart index e85d450f..36c944e6 100644 --- a/packages/nextcloud/test/news_test.dart +++ b/packages/nextcloud/test/news_test.dart @@ -1,6 +1,3 @@ -@Retry(3) -library news_test; - import 'dart:async'; import 'dart:io'; @@ -10,265 +7,270 @@ import 'package:test/test.dart'; import 'helper.dart'; void main() { - group('news', () { - late DockerImage image; - late HttpServer rssServer; - setUpAll(() async { - image = await getDockerImage(); - rssServer = await getRssServer(); - }); - tearDownAll(() async => rssServer.close(force: true)); - - late DockerContainer container; - late TestNextcloudClient client; - setUp(() async { - container = await getDockerContainer(image); - client = await getTestClient(container); - }); - tearDown(() => container.destroy()); - - Future addWikipediaFeed([final int? folderID]) async => client.news.addFeed( - url: 'http://host.docker.internal:${rssServer.port}/wikipedia.xml', - folderId: folderID, - ); - - Future addNasaFeed() async => client.news.addFeed( - url: 'http://host.docker.internal:${rssServer.port}/nasa.xml', + group( + 'news', + () { + late DockerImage image; + late HttpServer rssServer; + setUpAll(() async { + image = await getDockerImage(); + rssServer = await getRssServer(); + }); + tearDownAll(() async => rssServer.close(force: true)); + + late DockerContainer container; + late TestNextcloudClient client; + setUp(() async { + container = await getDockerContainer(image); + client = await getTestClient(container); + }); + tearDown(() => container.destroy()); + + Future addWikipediaFeed([final int? folderID]) async => client.news.addFeed( + url: 'http://host.docker.internal:${rssServer.port}/wikipedia.xml', + folderId: folderID, + ); + + Future addNasaFeed() async => client.news.addFeed( + url: 'http://host.docker.internal:${rssServer.port}/nasa.xml', + ); + + test('Is supported', () async { + final (supported, _) = await client.news.isSupported(); + expect(supported, isTrue); + }); + + test('Add feed', () async { + var response = await client.news.listFeeds(); + expect(response.starredCount, 0); + expect(response.newestItemId, null); + expect(response.feeds, hasLength(0)); + + response = await addWikipediaFeed(); + expect(response.starredCount, null); + expect(response.newestItemId, isNotNull); + expect(response.feeds, hasLength(1)); + expect(response.feeds[0].url, 'http://host.docker.internal:${rssServer.port}/wikipedia.xml'); + + response = await client.news.listFeeds(); + expect(response.starredCount, 0); + expect(response.newestItemId, isNotNull); + expect(response.feeds, hasLength(1)); + expect(response.feeds[0].url, 'http://host.docker.internal:${rssServer.port}/wikipedia.xml'); + }); + + test('Rename feed', () async { + var response = await addWikipediaFeed(); + expect(response.feeds[0].title, 'Wikipedia featured articles feed'); + + await client.news.renameFeed( + feedId: 1, + feedTitle: 'test1', ); - test('Is supported', () async { - final (supported, _) = await client.news.isSupported(); - expect(supported, isTrue); - }); - - test('Add feed', () async { - var response = await client.news.listFeeds(); - expect(response.starredCount, 0); - expect(response.newestItemId, null); - expect(response.feeds, hasLength(0)); - - response = await addWikipediaFeed(); - expect(response.starredCount, null); - expect(response.newestItemId, isNotNull); - expect(response.feeds, hasLength(1)); - expect(response.feeds[0].url, 'http://host.docker.internal:${rssServer.port}/wikipedia.xml'); - - response = await client.news.listFeeds(); - expect(response.starredCount, 0); - expect(response.newestItemId, isNotNull); - expect(response.feeds, hasLength(1)); - expect(response.feeds[0].url, 'http://host.docker.internal:${rssServer.port}/wikipedia.xml'); - }); - - test('Rename feed', () async { - var response = await addWikipediaFeed(); - expect(response.feeds[0].title, 'Wikipedia featured articles feed'); - - await client.news.renameFeed( - feedId: 1, - feedTitle: 'test1', - ); - - response = await client.news.listFeeds(); - expect(response.feeds[0].title, 'test1'); - }); + response = await client.news.listFeeds(); + expect(response.feeds[0].title, 'test1'); + }); - test('Move feed to folder', () async { - await client.news.createFolder(name: 'test1'); - await addWikipediaFeed(); - await client.news.moveFeed( - feedId: 1, - folderId: 1, - ); + test('Move feed to folder', () async { + await client.news.createFolder(name: 'test1'); + await addWikipediaFeed(); + await client.news.moveFeed( + feedId: 1, + folderId: 1, + ); - final response = await client.news.listFolders(); - expect(response.folders, hasLength(1)); - expect(response.folders[0].id, 1); - expect(response.folders[0].name, 'test1'); - expect(response.folders[0].opened, true); - expect(response.folders[0].feeds, hasLength(0)); - }); + final response = await client.news.listFolders(); + expect(response.folders, hasLength(1)); + expect(response.folders[0].id, 1); + expect(response.folders[0].name, 'test1'); + expect(response.folders[0].opened, true); + expect(response.folders[0].feeds, hasLength(0)); + }); - test('Mark feed as read', () async { - final feedsResponse = await addWikipediaFeed(); + test('Mark feed as read', () async { + final feedsResponse = await addWikipediaFeed(); - var articlesResponse = await client.news.listArticles(type: NewsListType.unread.index); - expect(articlesResponse.items.length, greaterThan(0)); + var articlesResponse = await client.news.listArticles(type: NewsListType.unread.index); + expect(articlesResponse.items.length, greaterThan(0)); - await client.news.markFeedAsRead( - feedId: feedsResponse.feeds[0].id, - newestItemId: feedsResponse.newestItemId!, - ); + await client.news.markFeedAsRead( + feedId: feedsResponse.feeds[0].id, + newestItemId: feedsResponse.newestItemId!, + ); - articlesResponse = await client.news.listArticles(type: NewsListType.unread.index); - expect(articlesResponse.items, hasLength(0)); - }); + articlesResponse = await client.news.listArticles(type: NewsListType.unread.index); + expect(articlesResponse.items, hasLength(0)); + }); - test('List articles', () async { - var response = await client.news.listArticles(); - expect(response.items, hasLength(0)); + test('List articles', () async { + var response = await client.news.listArticles(); + expect(response.items, hasLength(0)); - await addWikipediaFeed(); + await addWikipediaFeed(); - response = await client.news.listArticles(); - expect(response.items.length, greaterThan(0)); - expect(response.items[0].body, isNotNull); - expect(response.items[0].feedId, 1); - expect(response.items[0].unread, true); - expect(response.items[0].starred, false); - }); + response = await client.news.listArticles(); + expect(response.items.length, greaterThan(0)); + expect(response.items[0].body, isNotNull); + expect(response.items[0].feedId, 1); + expect(response.items[0].unread, true); + expect(response.items[0].starred, false); + }); - test('List updated articles', () async { - // Testing this is not easy, because we can't depend on an external source to update the feed - // Therefore we just add a second feed and check that the articles returned after a certain modified timestamp - // are exactly those of the new feed. - // Now that I think of it, maybe we could host our own feed and update that way, but this works for now. + test('List updated articles', () async { + // Testing this is not easy, because we can't depend on an external source to update the feed + // Therefore we just add a second feed and check that the articles returned after a certain modified timestamp + // are exactly those of the new feed. + // Now that I think of it, maybe we could host our own feed and update that way, but this works for now. - await addWikipediaFeed(); + await addWikipediaFeed(); - var response = await client.news.listArticles(); - final wikipediaArticles = response.items.length; - expect(wikipediaArticles, greaterThan(0)); + var response = await client.news.listArticles(); + final wikipediaArticles = response.items.length; + expect(wikipediaArticles, greaterThan(0)); - await addNasaFeed(); + await addNasaFeed(); - response = await client.news.listArticles(); - final nasaArticles = response.items.length - wikipediaArticles; - expect(nasaArticles, greaterThan(0)); + response = await client.news.listArticles(); + final nasaArticles = response.items.length - wikipediaArticles; + expect(nasaArticles, greaterThan(0)); - response = await client.news.listUpdatedArticles( - lastModified: response.items[response.items.length - 1 - nasaArticles].lastModified, - ); - expect(response.items, hasLength(nasaArticles)); - }); + response = await client.news.listUpdatedArticles( + lastModified: response.items[response.items.length - 1 - nasaArticles].lastModified, + ); + expect(response.items, hasLength(nasaArticles)); + }); - test('Mark article as read', () async { - await addWikipediaFeed(); + test('Mark article as read', () async { + await addWikipediaFeed(); - var response = await client.news.listArticles(type: NewsListType.unread.index); - final unreadArticles = response.items.length; - expect(unreadArticles, greaterThan(0)); + var response = await client.news.listArticles(type: NewsListType.unread.index); + final unreadArticles = response.items.length; + expect(unreadArticles, greaterThan(0)); - await client.news.markArticleAsRead( - itemId: response.items[0].id, - ); - response = await client.news.listArticles(type: NewsListType.unread.index); - expect(response.items, hasLength(unreadArticles - 1)); - }); - - test('Mark article as unread', () async { - await addWikipediaFeed(); - - var response = await client.news.listArticles(type: NewsListType.unread.index); - final readArticle = response.items[0]; - await client.news.markArticleAsRead(itemId: readArticle.id); - response = await client.news.listArticles(type: NewsListType.unread.index); - final unreadArticles = response.items.length; - expect(unreadArticles, greaterThan(0)); - - await client.news.markArticleAsUnread(itemId: readArticle.id); - response = await client.news.listArticles(type: NewsListType.unread.index); - expect(response.items, hasLength(unreadArticles + 1)); - }); - - test('Star article', () async { - await addWikipediaFeed(); - - var response = await client.news.listArticles(type: NewsListType.starred.index); - final starredArticles = response.items.length; - expect(starredArticles, 0); - - response = await client.news.listArticles(); - await client.news.starArticle( - itemId: response.items[0].id, - ); - response = await client.news.listArticles(type: NewsListType.starred.index); - expect(response.items, hasLength(1)); - }); + await client.news.markArticleAsRead( + itemId: response.items[0].id, + ); + response = await client.news.listArticles(type: NewsListType.unread.index); + expect(response.items, hasLength(unreadArticles - 1)); + }); + + test('Mark article as unread', () async { + await addWikipediaFeed(); + + var response = await client.news.listArticles(type: NewsListType.unread.index); + final readArticle = response.items[0]; + await client.news.markArticleAsRead(itemId: readArticle.id); + response = await client.news.listArticles(type: NewsListType.unread.index); + final unreadArticles = response.items.length; + expect(unreadArticles, greaterThan(0)); + + await client.news.markArticleAsUnread(itemId: readArticle.id); + response = await client.news.listArticles(type: NewsListType.unread.index); + expect(response.items, hasLength(unreadArticles + 1)); + }); + + test('Star article', () async { + await addWikipediaFeed(); + + var response = await client.news.listArticles(type: NewsListType.starred.index); + final starredArticles = response.items.length; + expect(starredArticles, 0); + + response = await client.news.listArticles(); + await client.news.starArticle( + itemId: response.items[0].id, + ); + response = await client.news.listArticles(type: NewsListType.starred.index); + expect(response.items, hasLength(1)); + }); - test('Unstar article', () async { - await addWikipediaFeed(); + test('Unstar article', () async { + await addWikipediaFeed(); - var response = await client.news.listArticles(); - final item = response.items[0]; + var response = await client.news.listArticles(); + final item = response.items[0]; - await client.news.starArticle( - itemId: item.id, - ); - response = await client.news.listArticles(type: NewsListType.starred.index); - expect(response.items, hasLength(1)); + await client.news.starArticle( + itemId: item.id, + ); + response = await client.news.listArticles(type: NewsListType.starred.index); + expect(response.items, hasLength(1)); - await client.news.unstarArticle( - itemId: item.id, - ); - response = await client.news.listArticles(type: NewsListType.starred.index); - expect(response.items, hasLength(0)); - }); - - test('Create folder', () async { - var response = await client.news.listFolders(); - expect(response.folders, hasLength(0)); - - response = await client.news.createFolder(name: 'test1'); - expect(response.folders, hasLength(1)); - expect(response.folders[0].id, 1); - expect(response.folders[0].name, 'test1'); - expect(response.folders[0].opened, true); - expect(response.folders[0].feeds, hasLength(0)); - - response = await client.news.listFolders(); - expect(response.folders, hasLength(1)); - expect(response.folders[0].id, 1); - expect(response.folders[0].name, 'test1'); - expect(response.folders[0].opened, true); - expect(response.folders[0].feeds, hasLength(0)); - }); - - test('List folders', () async { - var response = await client.news.listFolders(); - expect(response.folders, hasLength(0)); - - await client.news.createFolder(name: 'test1'); - await client.news.createFolder(name: 'test2'); - - response = response = await client.news.listFolders(); - expect(response.folders, hasLength(2)); - expect(response.folders[0].id, 1); - expect(response.folders[0].name, 'test1'); - expect(response.folders[0].opened, true); - expect(response.folders[0].feeds, hasLength(0)); - expect(response.folders[1].id, 2); - expect(response.folders[1].name, 'test2'); - expect(response.folders[1].opened, true); - expect(response.folders[1].feeds, hasLength(0)); - }); - - test('Add feed to folder', () async { - await client.news.createFolder(name: 'test1'); - final response = await addWikipediaFeed(1); - expect(response.starredCount, null); - expect(response.newestItemId, isNotNull); - expect(response.feeds, hasLength(1)); - expect(response.feeds[0].folderId, 1); - expect(response.feeds[0].url, 'http://host.docker.internal:${rssServer.port}/wikipedia.xml'); - }); - - test('Mark folder as read', () async { - final foldersResponse = await client.news.createFolder(name: 'test1'); - final feedsResponse = await addWikipediaFeed(1); - - var articlesResponse = await client.news.listArticles(type: NewsListType.unread.index); - expect(articlesResponse.items.length, greaterThan(0)); - - await client.news.markFolderAsRead( - folderId: foldersResponse.folders[0].id, - newestItemId: feedsResponse.newestItemId!, - ); + await client.news.unstarArticle( + itemId: item.id, + ); + response = await client.news.listArticles(type: NewsListType.starred.index); + expect(response.items, hasLength(0)); + }); + + test('Create folder', () async { + var response = await client.news.listFolders(); + expect(response.folders, hasLength(0)); + + response = await client.news.createFolder(name: 'test1'); + expect(response.folders, hasLength(1)); + expect(response.folders[0].id, 1); + expect(response.folders[0].name, 'test1'); + expect(response.folders[0].opened, true); + expect(response.folders[0].feeds, hasLength(0)); + + response = await client.news.listFolders(); + expect(response.folders, hasLength(1)); + expect(response.folders[0].id, 1); + expect(response.folders[0].name, 'test1'); + expect(response.folders[0].opened, true); + expect(response.folders[0].feeds, hasLength(0)); + }); + + test('List folders', () async { + var response = await client.news.listFolders(); + expect(response.folders, hasLength(0)); + + await client.news.createFolder(name: 'test1'); + await client.news.createFolder(name: 'test2'); + + response = response = await client.news.listFolders(); + expect(response.folders, hasLength(2)); + expect(response.folders[0].id, 1); + expect(response.folders[0].name, 'test1'); + expect(response.folders[0].opened, true); + expect(response.folders[0].feeds, hasLength(0)); + expect(response.folders[1].id, 2); + expect(response.folders[1].name, 'test2'); + expect(response.folders[1].opened, true); + expect(response.folders[1].feeds, hasLength(0)); + }); + + test('Add feed to folder', () async { + await client.news.createFolder(name: 'test1'); + final response = await addWikipediaFeed(1); + expect(response.starredCount, null); + expect(response.newestItemId, isNotNull); + expect(response.feeds, hasLength(1)); + expect(response.feeds[0].folderId, 1); + expect(response.feeds[0].url, 'http://host.docker.internal:${rssServer.port}/wikipedia.xml'); + }); + + test('Mark folder as read', () async { + final foldersResponse = await client.news.createFolder(name: 'test1'); + final feedsResponse = await addWikipediaFeed(1); + + var articlesResponse = await client.news.listArticles(type: NewsListType.unread.index); + expect(articlesResponse.items.length, greaterThan(0)); + + await client.news.markFolderAsRead( + folderId: foldersResponse.folders[0].id, + newestItemId: feedsResponse.newestItemId!, + ); - articlesResponse = await client.news.listArticles(type: NewsListType.unread.index); - expect(articlesResponse.items, hasLength(0)); - }); - }); + articlesResponse = await client.news.listArticles(type: NewsListType.unread.index); + expect(articlesResponse.items, hasLength(0)); + }); + }, + retry: retryCount, + timeout: timeout, + ); } Future getRssServer() async { diff --git a/packages/nextcloud/test/notes_test.dart b/packages/nextcloud/test/notes_test.dart index 12651903..149298a1 100644 --- a/packages/nextcloud/test/notes_test.dart +++ b/packages/nextcloud/test/notes_test.dart @@ -1,143 +1,145 @@ -@Retry(3) -library notes_test; - import 'package:nextcloud/nextcloud.dart'; import 'package:test/test.dart'; import 'helper.dart'; void main() { - group('notes', () { - late DockerImage image; - setUpAll(() async => image = await getDockerImage()); - - late DockerContainer container; - late TestNextcloudClient client; - setUp(() async { - container = await getDockerContainer(image); - client = await getTestClient(container); - }); - tearDown(() => container.destroy()); - - test('Is supported', () async { - final (supported, _) = client.notes.isSupported((await client.core.ocs.getCapabilities()).ocs.data); - expect(supported, isTrue); - }); - - test('Create note favorite', () async { - final response = await client.notes.createNote( - title: 'a', - content: 'b', - category: 'c', - favorite: 1, - ); - expect(response.id, isPositive); - expect(response.title, 'a'); - expect(response.content, 'b'); - expect(response.category, 'c'); - expect(response.favorite, true); - expect(response.readonly, false); - expect(response.etag, isNotNull); - expect(response.modified, isNotNull); - }); - - test('Create note not favorite', () async { - final response = await client.notes.createNote( - title: 'a', - content: 'b', - category: 'c', - ); - expect(response.id, isPositive); - expect(response.title, 'a'); - expect(response.content, 'b'); - expect(response.category, 'c'); - expect(response.favorite, false); - expect(response.readonly, false); - expect(response.etag, isNotNull); - expect(response.modified, isNotNull); - }); - - test('Get notes', () async { - await client.notes.createNote(title: 'a'); - await client.notes.createNote(title: 'b'); - - final response = await client.notes.getNotes(); - expect(response, hasLength(2)); - expect(response[0].title, 'a'); - expect(response[1].title, 'b'); - }); - - test('Get note', () async { - final response = await client.notes.getNote( - id: (await client.notes.createNote(title: 'a')).id, - ); - expect(response.title, 'a'); - }); - - test('Update note', () async { - final id = (await client.notes.createNote(title: 'a')).id; - await client.notes.updateNote( - id: id, - title: 'b', - ); - - final response = await client.notes.getNote(id: id); - expect(response.title, 'b'); - }); - - test('Update note fail changed on server', () async { - final response = await client.notes.createNote(title: 'a'); - await client.notes.updateNote( - id: response.id, - title: 'b', - ifMatch: '"${response.etag}"', - ); - expect( - () => client.notes.updateNote( + group( + 'notes', + () { + late DockerImage image; + setUpAll(() async => image = await getDockerImage()); + + late DockerContainer container; + late TestNextcloudClient client; + setUp(() async { + container = await getDockerContainer(image); + client = await getTestClient(container); + }); + tearDown(() => container.destroy()); + + test('Is supported', () async { + final (supported, _) = client.notes.isSupported((await client.core.ocs.getCapabilities()).ocs.data); + expect(supported, isTrue); + }); + + test('Create note favorite', () async { + final response = await client.notes.createNote( + title: 'a', + content: 'b', + category: 'c', + favorite: 1, + ); + expect(response.id, isPositive); + expect(response.title, 'a'); + expect(response.content, 'b'); + expect(response.category, 'c'); + expect(response.favorite, true); + expect(response.readonly, false); + expect(response.etag, isNotNull); + expect(response.modified, isNotNull); + }); + + test('Create note not favorite', () async { + final response = await client.notes.createNote( + title: 'a', + content: 'b', + category: 'c', + ); + expect(response.id, isPositive); + expect(response.title, 'a'); + expect(response.content, 'b'); + expect(response.category, 'c'); + expect(response.favorite, false); + expect(response.readonly, false); + expect(response.etag, isNotNull); + expect(response.modified, isNotNull); + }); + + test('Get notes', () async { + await client.notes.createNote(title: 'a'); + await client.notes.createNote(title: 'b'); + + final response = await client.notes.getNotes(); + expect(response, hasLength(2)); + expect(response[0].title, 'a'); + expect(response[1].title, 'b'); + }); + + test('Get note', () async { + final response = await client.notes.getNote( + id: (await client.notes.createNote(title: 'a')).id, + ); + expect(response.title, 'a'); + }); + + test('Update note', () async { + final id = (await client.notes.createNote(title: 'a')).id; + await client.notes.updateNote( + id: id, + title: 'b', + ); + + final response = await client.notes.getNote(id: id); + expect(response.title, 'b'); + }); + + test('Update note fail changed on server', () async { + final response = await client.notes.createNote(title: 'a'); + await client.notes.updateNote( id: response.id, - title: 'c', + title: 'b', ifMatch: '"${response.etag}"', - ), - throwsA(predicate((final e) => (e! as DynamiteApiException).statusCode == 412)), - ); - }); - - test('Delete note', () async { - final id = (await client.notes.createNote(title: 'a')).id; - - var response = await client.notes.getNotes(); - expect(response, hasLength(1)); - - await client.notes.deleteNote(id: id); - - response = await client.notes.getNotes(); - expect(response, hasLength(0)); - }); - - test('Get settings', () async { - final response = await client.notes.getSettings(); - expect(response.notesPath, 'Notes'); - expect(response.fileSuffix, '.md'); - expect(response.noteMode, NotesSettings_NoteMode.rich); - }); - - test('Update settings', () async { - var response = await client.notes.updateSettings( - settings: NotesSettings( - (final b) => b - ..notesPath = 'Test Notes' - ..fileSuffix = '.txt' - ..noteMode = NotesSettings_NoteMode.preview, - ), - ); - expect(response.notesPath, 'Test Notes'); - expect(response.fileSuffix, '.txt'); - expect(response.noteMode, NotesSettings_NoteMode.preview); - - response = await client.notes.getSettings(); - expect(response.notesPath, 'Test Notes'); - expect(response.fileSuffix, '.txt'); - expect(response.noteMode, NotesSettings_NoteMode.preview); - }); - }); + ); + expect( + () => client.notes.updateNote( + id: response.id, + title: 'c', + ifMatch: '"${response.etag}"', + ), + throwsA(predicate((final e) => (e! as DynamiteApiException).statusCode == 412)), + ); + }); + + test('Delete note', () async { + final id = (await client.notes.createNote(title: 'a')).id; + + var response = await client.notes.getNotes(); + expect(response, hasLength(1)); + + await client.notes.deleteNote(id: id); + + response = await client.notes.getNotes(); + expect(response, hasLength(0)); + }); + + test('Get settings', () async { + final response = await client.notes.getSettings(); + expect(response.notesPath, 'Notes'); + expect(response.fileSuffix, '.md'); + expect(response.noteMode, NotesSettings_NoteMode.rich); + }); + + test('Update settings', () async { + var response = await client.notes.updateSettings( + settings: NotesSettings( + (final b) => b + ..notesPath = 'Test Notes' + ..fileSuffix = '.txt' + ..noteMode = NotesSettings_NoteMode.preview, + ), + ); + expect(response.notesPath, 'Test Notes'); + expect(response.fileSuffix, '.txt'); + expect(response.noteMode, NotesSettings_NoteMode.preview); + + response = await client.notes.getSettings(); + expect(response.notesPath, 'Test Notes'); + expect(response.fileSuffix, '.txt'); + expect(response.noteMode, NotesSettings_NoteMode.preview); + }); + }, + retry: retryCount, + timeout: timeout, + ); } diff --git a/packages/nextcloud/test/notifications_test.dart b/packages/nextcloud/test/notifications_test.dart index 002fdd1c..cbbd13bc 100644 --- a/packages/nextcloud/test/notifications_test.dart +++ b/packages/nextcloud/test/notifications_test.dart @@ -1,6 +1,3 @@ -@Retry(3) -library notifications_test; - import 'dart:async'; import 'package:nextcloud/nextcloud.dart'; @@ -99,43 +96,48 @@ void main() { }); }); - group('push notifications', () { - late DockerImage image; - setUpAll(() async => image = await getDockerImage()); - - late DockerContainer container; - late TestNextcloudClient client; - setUp(() async { - container = await getDockerContainer(image); - client = await getTestClient( - container, - username: 'admin', - ); - }); - tearDown(() => container.destroy()); - - // The key size has to be 2048, other sizes are not accepted by Nextcloud (at the moment at least) - // ignore: avoid_redundant_argument_values - RSAKeypair generateKeypair() => RSAKeypair.fromRandom(keySize: 2048); - - test('Register and remove push device', () async { - const pushToken = '789'; - final keypair = generateKeypair(); - - final subscription = (await client.notifications.registerDevice( - pushTokenHash: generatePushTokenHash(pushToken), - devicePublicKey: keypair.publicKey.toFormattedPEM(), - proxyServer: 'https://example.com/', - )) - .ocs - .data; - expect(subscription.publicKey, hasLength(451)); - RSAPublicKey.fromPEM(subscription.publicKey); - expect(subscription.deviceIdentifier, isNotEmpty); - expect(subscription.signature, isNotEmpty); - expect(subscription.message, isNull); - - await client.notifications.removeDevice(); - }); - }); + group( + 'push notifications', + () { + late DockerImage image; + setUpAll(() async => image = await getDockerImage()); + + late DockerContainer container; + late TestNextcloudClient client; + setUp(() async { + container = await getDockerContainer(image); + client = await getTestClient( + container, + username: 'admin', + ); + }); + tearDown(() => container.destroy()); + + // The key size has to be 2048, other sizes are not accepted by Nextcloud (at the moment at least) + // ignore: avoid_redundant_argument_values + RSAKeypair generateKeypair() => RSAKeypair.fromRandom(keySize: 2048); + + test('Register and remove push device', () async { + const pushToken = '789'; + final keypair = generateKeypair(); + + final subscription = (await client.notifications.registerDevice( + pushTokenHash: generatePushTokenHash(pushToken), + devicePublicKey: keypair.publicKey.toFormattedPEM(), + proxyServer: 'https://example.com/', + )) + .ocs + .data; + expect(subscription.publicKey, hasLength(451)); + RSAPublicKey.fromPEM(subscription.publicKey); + expect(subscription.deviceIdentifier, isNotEmpty); + expect(subscription.signature, isNotEmpty); + expect(subscription.message, isNull); + + await client.notifications.removeDevice(); + }); + }, + retry: retryCount, + timeout: timeout, + ); } diff --git a/packages/nextcloud/test/provisioning_api_test.dart b/packages/nextcloud/test/provisioning_api_test.dart index 6cc65ed1..cff3508d 100644 --- a/packages/nextcloud/test/provisioning_api_test.dart +++ b/packages/nextcloud/test/provisioning_api_test.dart @@ -1,54 +1,56 @@ -@Retry(3) -library provisioning_api_test; - import 'package:test/test.dart'; import 'helper.dart'; void main() { - group('provisioning_api', () { - late DockerImage image; - setUpAll(() async => image = await getDockerImage()); - - late DockerContainer container; - late TestNextcloudClient client; - setUp(() async { - container = await getDockerContainer(image); - client = await getTestClient( - container, - username: 'admin', - ); - }); - tearDown(() => container.destroy()); - - group('Users', () { - test('Get current user', () async { - final user = await client.provisioningApi.users.getCurrentUser(); - expect(user.ocs.data.id, 'admin'); - expect(user.ocs.data.displayName, 'admin'); - expect(user.ocs.data.displaynameScope, 'v2-federated'); - expect(user.ocs.data.language, 'en'); + group( + 'provisioning_api', + () { + late DockerImage image; + setUpAll(() async => image = await getDockerImage()); + + late DockerContainer container; + late TestNextcloudClient client; + setUp(() async { + container = await getDockerContainer(image); + client = await getTestClient( + container, + username: 'admin', + ); }); - - test('Get user by username', () async { - final user = await client.provisioningApi.users.getUser(userId: 'user1'); - expect(user.ocs.data.id, 'user1'); - expect(user.ocs.data.displayname, 'User One'); - expect(user.ocs.data.displaynameScope, null); - expect(user.ocs.data.language, 'en'); + tearDown(() => container.destroy()); + + group('Users', () { + test('Get current user', () async { + final user = await client.provisioningApi.users.getCurrentUser(); + expect(user.ocs.data.id, 'admin'); + expect(user.ocs.data.displayName, 'admin'); + expect(user.ocs.data.displaynameScope, 'v2-federated'); + expect(user.ocs.data.language, 'en'); + }); + + test('Get user by username', () async { + final user = await client.provisioningApi.users.getUser(userId: 'user1'); + expect(user.ocs.data.id, 'user1'); + expect(user.ocs.data.displayname, 'User One'); + expect(user.ocs.data.displaynameScope, null); + expect(user.ocs.data.language, 'en'); + }); }); - }); - group('Apps', () { - test('Get apps', () async { - final response = await client.provisioningApi.apps.getApps(); - expect(response.ocs.data.apps, hasLength(39)); + group('Apps', () { + test('Get apps', () async { + final response = await client.provisioningApi.apps.getApps(); + expect(response.ocs.data.apps, hasLength(39)); - for (final id in response.ocs.data.apps) { - final app = await client.provisioningApi.apps.getAppInfo(app: id); - expect(app.ocs.data.id, isNotEmpty); - } + for (final id in response.ocs.data.apps) { + final app = await client.provisioningApi.apps.getAppInfo(app: id); + expect(app.ocs.data.id, isNotEmpty); + } + }); }); - }); - }); + }, + retry: retryCount, + timeout: timeout, + ); } diff --git a/packages/nextcloud/test/uppush_test.dart b/packages/nextcloud/test/uppush_test.dart index 3aa5fc9b..bdd98898 100644 --- a/packages/nextcloud/test/uppush_test.dart +++ b/packages/nextcloud/test/uppush_test.dart @@ -1,67 +1,69 @@ -@Retry(3) -library uppush_test; - import 'package:test/test.dart'; import 'helper.dart'; void main() { - group('uppush', () { - late DockerImage image; - setUpAll(() async => image = await getDockerImage()); + group( + 'uppush', + () { + late DockerImage image; + setUpAll(() async => image = await getDockerImage()); - late DockerContainer container; - late TestNextcloudClient client; - setUp(() async { - container = await getDockerContainer(image); - client = await getTestClient( - container, - username: 'admin', - ); - }); - tearDown(() => container.destroy()); + late DockerContainer container; + late TestNextcloudClient client; + setUp(() async { + container = await getDockerContainer(image); + client = await getTestClient( + container, + username: 'admin', + ); + }); + tearDown(() => container.destroy()); - test('Is installed', () async { - final response = await client.uppush.check(); - expect(response.success, isTrue); - }); + test('Is installed', () async { + final response = await client.uppush.check(); + expect(response.success, isTrue); + }); - test('Set keepalive', () async { - final response = await client.uppush.setKeepalive(keepalive: 10); - expect(response.success, isTrue); - }); + test('Set keepalive', () async { + final response = await client.uppush.setKeepalive(keepalive: 10); + expect(response.success, isTrue); + }); - test('Create device', () async { - final response = await client.uppush.createDevice(deviceName: 'Test'); - expect(response.success, isTrue); - expect(response.deviceId, isNotEmpty); - }); + test('Create device', () async { + final response = await client.uppush.createDevice(deviceName: 'Test'); + expect(response.success, isTrue); + expect(response.deviceId, isNotEmpty); + }); - test('Delete device', () async { - final deviceId = (await client.uppush.createDevice(deviceName: 'Test')).deviceId; + test('Delete device', () async { + final deviceId = (await client.uppush.createDevice(deviceName: 'Test')).deviceId; - final response = await client.uppush.deleteDevice(deviceId: deviceId); - expect(response.success, isTrue); - }); + final response = await client.uppush.deleteDevice(deviceId: deviceId); + expect(response.success, isTrue); + }); - test('Create app', () async { - final deviceId = (await client.uppush.createDevice(deviceName: 'Test')).deviceId; + test('Create app', () async { + final deviceId = (await client.uppush.createDevice(deviceName: 'Test')).deviceId; - final response = await client.uppush.createApp(deviceId: deviceId, appName: 'Test'); - expect(response.success, isTrue); - expect(response.token, isNotEmpty); - }); + final response = await client.uppush.createApp(deviceId: deviceId, appName: 'Test'); + expect(response.success, isTrue); + expect(response.token, isNotEmpty); + }); - test('UnifiedPush discovery', () async { - final response = await client.uppush.unifiedpushDiscovery(token: 'example'); - expect(response.unifiedpush.version, 1); - }); + test('UnifiedPush discovery', () async { + final response = await client.uppush.unifiedpushDiscovery(token: 'example'); + expect(response.unifiedpush.version, 1); + }); - test('Matrix gateway discovery', () async { - final response = await client.uppush.gatewayMatrixDiscovery(); - expect(response.unifiedpush.gateway, 'matrix'); - }); + test('Matrix gateway discovery', () async { + final response = await client.uppush.gatewayMatrixDiscovery(); + expect(response.unifiedpush.gateway, 'matrix'); + }); - // Deleting an app, sending a notification (also via matrix gateway) or listening for notifications is not possible because redis is not set up - }); + // Deleting an app, sending a notification (also via matrix gateway) or listening for notifications is not possible because redis is not set up + }, + retry: retryCount, + timeout: timeout, + ); } diff --git a/packages/nextcloud/test/user_status_test.dart b/packages/nextcloud/test/user_status_test.dart index a18c8b14..1c1b0d00 100644 --- a/packages/nextcloud/test/user_status_test.dart +++ b/packages/nextcloud/test/user_status_test.dart @@ -1,174 +1,176 @@ -@Retry(3) -library user_status_test; - import 'package:nextcloud/nextcloud.dart'; import 'package:test/test.dart'; import 'helper.dart'; void main() { - group('user_status', () { - late DockerImage image; - setUpAll(() async => image = await getDockerImage()); - - late DockerContainer container; - late TestNextcloudClient client; - setUp(() async { - container = await getDockerContainer(image); - client = await getTestClient(container); - }); - tearDown(() => container.destroy()); - - test('Find all predefined statuses', () async { - final expectedStatusIDs = ['meeting', 'commuting', 'remote-work', 'sick-leave', 'vacationing']; - final response = await client.userStatus.predefinedStatus.findAll(); - expect(response.ocs.data, hasLength(5)); - final responseIDs = response.ocs.data.map((final status) => status.id); - expect(expectedStatusIDs.map(responseIDs.contains).contains(false), false); - for (final status in response.ocs.data) { - expect(status.icon, isNotNull); - expect(status.message, isNotNull); - } - - final meeting = response.ocs.data.singleWhere((final s) => s.id == 'meeting').clearAt!; - expect(meeting.type, UserStatusClearAt_Type.period); - expect(meeting.time.$int, 3600); - - final commuting = response.ocs.data.singleWhere((final s) => s.id == 'commuting').clearAt!; - expect(commuting.type, UserStatusClearAt_Type.period); - expect(commuting.time.$int, 1800); - - final remoteWork = response.ocs.data.singleWhere((final s) => s.id == 'remote-work').clearAt!; - expect(remoteWork.type, UserStatusClearAt_Type.endOf); - expect(remoteWork.time.clearAtTimeType, UserStatusClearAtTimeType.day); - - final sickLeave = response.ocs.data.singleWhere((final s) => s.id == 'sick-leave').clearAt!; - expect(sickLeave.type, UserStatusClearAt_Type.endOf); - expect(sickLeave.time.clearAtTimeType, UserStatusClearAtTimeType.day); - - final vacationing = response.ocs.data.singleWhere((final s) => s.id == 'vacationing').clearAt; - expect(vacationing, null); - }); - - test('Set status', () async { - final response = await client.userStatus.userStatus.setStatus(statusType: 'online'); - - expect(response.ocs.data.public.userId, 'user1'); - expect(response.ocs.data.public.message, null); - expect(response.ocs.data.private1.messageId, null); - expect(response.ocs.data.private1.messageIsPredefined, false); - expect(response.ocs.data.public.icon, null); - expect(response.ocs.data.public.clearAt, null); - expect(response.ocs.data.public.status, 'online'); - expect(response.ocs.data.private1.statusIsUserDefined, true); - }); - - test('Get status', () async { - // There seems to be a bug in Nextcloud which makes getting the status fail before it has been set once. - // The error message from Nextcloud is "Could not create folder" - await client.userStatus.userStatus.setStatus(statusType: 'online'); - - final response = await client.userStatus.userStatus.getStatus(); - expect(response.ocs.data.public.userId, 'user1'); - expect(response.ocs.data.public.message, null); - expect(response.ocs.data.private1.messageId, null); - expect(response.ocs.data.private1.messageIsPredefined, false); - expect(response.ocs.data.public.icon, null); - expect(response.ocs.data.public.clearAt, null); - expect(response.ocs.data.public.status, 'online'); - expect(response.ocs.data.private1.statusIsUserDefined, true); - }); - - test('Find all statuses', () async { - var response = await client.userStatus.statuses.findAll(); - expect(response.ocs.data, hasLength(0)); - - await client.userStatus.userStatus.setStatus(statusType: 'online'); - - response = await client.userStatus.statuses.findAll(); - expect(response.ocs.data, hasLength(1)); - expect(response.ocs.data[0].userId, 'user1'); - expect(response.ocs.data[0].message, null); - expect(response.ocs.data[0].icon, null); - expect(response.ocs.data[0].clearAt, null); - expect(response.ocs.data[0].status, 'online'); - }); - - test('Find status', () async { - // Same as getting status - await client.userStatus.userStatus.setStatus(statusType: 'online'); - - final response = await client.userStatus.statuses.find(userId: 'user1'); - expect(response.ocs.data.userId, 'user1'); - expect(response.ocs.data.message, null); - expect(response.ocs.data.icon, null); - expect(response.ocs.data.clearAt, null); - expect(response.ocs.data.status, 'online'); - }); - - test('Set predefined message', () async { - final clearAt = DateTime.now().millisecondsSinceEpoch ~/ 1000 + 60; - final response = await client.userStatus.userStatus.setPredefinedMessage( - messageId: 'meeting', - clearAt: clearAt, - ); - expect(response.ocs.data.public.userId, 'user1'); - expect(response.ocs.data.public.message, null); - expect(response.ocs.data.private1.messageId, 'meeting'); - expect(response.ocs.data.private1.messageIsPredefined, true); - expect(response.ocs.data.public.icon, null); - expect(response.ocs.data.public.clearAt, clearAt); - expect(response.ocs.data.public.status, 'offline'); - expect(response.ocs.data.private1.statusIsUserDefined, false); - }); - - test('Set custom message', () async { - final clearAt = DateTime.now().millisecondsSinceEpoch ~/ 1000 + 60; - final response = await client.userStatus.userStatus.setCustomMessage( - statusIcon: '😀', - message: 'bla', - clearAt: clearAt, - ); - expect(response.ocs.data.public.userId, 'user1'); - expect(response.ocs.data.public.message, 'bla'); - expect(response.ocs.data.private1.messageId, null); - expect(response.ocs.data.private1.messageIsPredefined, false); - expect(response.ocs.data.public.icon, '😀'); - expect(response.ocs.data.public.clearAt, clearAt); - expect(response.ocs.data.public.status, 'offline'); - expect(response.ocs.data.private1.statusIsUserDefined, false); - }); - - test('Clear message', () async { - final clearAt = DateTime.now().millisecondsSinceEpoch ~/ 1000 + 60; - await client.userStatus.userStatus.setCustomMessage( - statusIcon: '😀', - message: 'bla', - clearAt: clearAt, - ); - await client.userStatus.userStatus.clearMessage(); - - final response = await client.userStatus.userStatus.getStatus(); - expect(response.ocs.data.public.userId, 'user1'); - expect(response.ocs.data.public.message, null); - expect(response.ocs.data.private1.messageId, null); - expect(response.ocs.data.private1.messageIsPredefined, false); - expect(response.ocs.data.public.icon, null); - expect(response.ocs.data.public.clearAt, null); - expect(response.ocs.data.public.status, 'offline'); - expect(response.ocs.data.private1.statusIsUserDefined, false); - }); - - test('Heartbeat', () async { - final response = await client.userStatus.heartbeat.heartbeat(status: 'online'); - expect(response.ocs.data.public.userId, 'user1'); - expect(response.ocs.data.public.message, null); - expect(response.ocs.data.private1.messageId, null); - expect(response.ocs.data.private1.messageIsPredefined, false); - expect(response.ocs.data.public.icon, null); - expect(response.ocs.data.public.clearAt, null); - expect(response.ocs.data.public.status, 'online'); - expect(response.ocs.data.private1.statusIsUserDefined, false); - }); - }); + group( + 'user_status', + () { + late DockerImage image; + setUpAll(() async => image = await getDockerImage()); + + late DockerContainer container; + late TestNextcloudClient client; + setUp(() async { + container = await getDockerContainer(image); + client = await getTestClient(container); + }); + tearDown(() => container.destroy()); + + test('Find all predefined statuses', () async { + final expectedStatusIDs = ['meeting', 'commuting', 'remote-work', 'sick-leave', 'vacationing']; + final response = await client.userStatus.predefinedStatus.findAll(); + expect(response.ocs.data, hasLength(5)); + final responseIDs = response.ocs.data.map((final status) => status.id); + expect(expectedStatusIDs.map(responseIDs.contains).contains(false), false); + for (final status in response.ocs.data) { + expect(status.icon, isNotNull); + expect(status.message, isNotNull); + } + + final meeting = response.ocs.data.singleWhere((final s) => s.id == 'meeting').clearAt!; + expect(meeting.type, UserStatusClearAt_Type.period); + expect(meeting.time.$int, 3600); + + final commuting = response.ocs.data.singleWhere((final s) => s.id == 'commuting').clearAt!; + expect(commuting.type, UserStatusClearAt_Type.period); + expect(commuting.time.$int, 1800); + + final remoteWork = response.ocs.data.singleWhere((final s) => s.id == 'remote-work').clearAt!; + expect(remoteWork.type, UserStatusClearAt_Type.endOf); + expect(remoteWork.time.clearAtTimeType, UserStatusClearAtTimeType.day); + + final sickLeave = response.ocs.data.singleWhere((final s) => s.id == 'sick-leave').clearAt!; + expect(sickLeave.type, UserStatusClearAt_Type.endOf); + expect(sickLeave.time.clearAtTimeType, UserStatusClearAtTimeType.day); + + final vacationing = response.ocs.data.singleWhere((final s) => s.id == 'vacationing').clearAt; + expect(vacationing, null); + }); + + test('Set status', () async { + final response = await client.userStatus.userStatus.setStatus(statusType: 'online'); + + expect(response.ocs.data.public.userId, 'user1'); + expect(response.ocs.data.public.message, null); + expect(response.ocs.data.private1.messageId, null); + expect(response.ocs.data.private1.messageIsPredefined, false); + expect(response.ocs.data.public.icon, null); + expect(response.ocs.data.public.clearAt, null); + expect(response.ocs.data.public.status, 'online'); + expect(response.ocs.data.private1.statusIsUserDefined, true); + }); + + test('Get status', () async { + // There seems to be a bug in Nextcloud which makes getting the status fail before it has been set once. + // The error message from Nextcloud is "Could not create folder" + await client.userStatus.userStatus.setStatus(statusType: 'online'); + + final response = await client.userStatus.userStatus.getStatus(); + expect(response.ocs.data.public.userId, 'user1'); + expect(response.ocs.data.public.message, null); + expect(response.ocs.data.private1.messageId, null); + expect(response.ocs.data.private1.messageIsPredefined, false); + expect(response.ocs.data.public.icon, null); + expect(response.ocs.data.public.clearAt, null); + expect(response.ocs.data.public.status, 'online'); + expect(response.ocs.data.private1.statusIsUserDefined, true); + }); + + test('Find all statuses', () async { + var response = await client.userStatus.statuses.findAll(); + expect(response.ocs.data, hasLength(0)); + + await client.userStatus.userStatus.setStatus(statusType: 'online'); + + response = await client.userStatus.statuses.findAll(); + expect(response.ocs.data, hasLength(1)); + expect(response.ocs.data[0].userId, 'user1'); + expect(response.ocs.data[0].message, null); + expect(response.ocs.data[0].icon, null); + expect(response.ocs.data[0].clearAt, null); + expect(response.ocs.data[0].status, 'online'); + }); + + test('Find status', () async { + // Same as getting status + await client.userStatus.userStatus.setStatus(statusType: 'online'); + + final response = await client.userStatus.statuses.find(userId: 'user1'); + expect(response.ocs.data.userId, 'user1'); + expect(response.ocs.data.message, null); + expect(response.ocs.data.icon, null); + expect(response.ocs.data.clearAt, null); + expect(response.ocs.data.status, 'online'); + }); + + test('Set predefined message', () async { + final clearAt = DateTime.now().millisecondsSinceEpoch ~/ 1000 + 60; + final response = await client.userStatus.userStatus.setPredefinedMessage( + messageId: 'meeting', + clearAt: clearAt, + ); + expect(response.ocs.data.public.userId, 'user1'); + expect(response.ocs.data.public.message, null); + expect(response.ocs.data.private1.messageId, 'meeting'); + expect(response.ocs.data.private1.messageIsPredefined, true); + expect(response.ocs.data.public.icon, null); + expect(response.ocs.data.public.clearAt, clearAt); + expect(response.ocs.data.public.status, 'offline'); + expect(response.ocs.data.private1.statusIsUserDefined, false); + }); + + test('Set custom message', () async { + final clearAt = DateTime.now().millisecondsSinceEpoch ~/ 1000 + 60; + final response = await client.userStatus.userStatus.setCustomMessage( + statusIcon: '😀', + message: 'bla', + clearAt: clearAt, + ); + expect(response.ocs.data.public.userId, 'user1'); + expect(response.ocs.data.public.message, 'bla'); + expect(response.ocs.data.private1.messageId, null); + expect(response.ocs.data.private1.messageIsPredefined, false); + expect(response.ocs.data.public.icon, '😀'); + expect(response.ocs.data.public.clearAt, clearAt); + expect(response.ocs.data.public.status, 'offline'); + expect(response.ocs.data.private1.statusIsUserDefined, false); + }); + + test('Clear message', () async { + final clearAt = DateTime.now().millisecondsSinceEpoch ~/ 1000 + 60; + await client.userStatus.userStatus.setCustomMessage( + statusIcon: '😀', + message: 'bla', + clearAt: clearAt, + ); + await client.userStatus.userStatus.clearMessage(); + + final response = await client.userStatus.userStatus.getStatus(); + expect(response.ocs.data.public.userId, 'user1'); + expect(response.ocs.data.public.message, null); + expect(response.ocs.data.private1.messageId, null); + expect(response.ocs.data.private1.messageIsPredefined, false); + expect(response.ocs.data.public.icon, null); + expect(response.ocs.data.public.clearAt, null); + expect(response.ocs.data.public.status, 'offline'); + expect(response.ocs.data.private1.statusIsUserDefined, false); + }); + + test('Heartbeat', () async { + final response = await client.userStatus.heartbeat.heartbeat(status: 'online'); + expect(response.ocs.data.public.userId, 'user1'); + expect(response.ocs.data.public.message, null); + expect(response.ocs.data.private1.messageId, null); + expect(response.ocs.data.private1.messageIsPredefined, false); + expect(response.ocs.data.public.icon, null); + expect(response.ocs.data.public.clearAt, null); + expect(response.ocs.data.public.status, 'online'); + expect(response.ocs.data.private1.statusIsUserDefined, false); + }); + }, + retry: retryCount, + timeout: timeout, + ); } diff --git a/packages/nextcloud/test/webdav_test.dart b/packages/nextcloud/test/webdav_test.dart index 83bc0a65..0ac3d0ef 100644 --- a/packages/nextcloud/test/webdav_test.dart +++ b/packages/nextcloud/test/webdav_test.dart @@ -1,6 +1,3 @@ -@Retry(3) -library webdav_test; - import 'dart:convert'; import 'dart:io'; import 'dart:math'; @@ -26,508 +23,514 @@ void main() { expect(() => WebDavClient.constructUri(baseURL), throwsA(isA())); }); - group('webdav', () { - late DockerImage image; - setUpAll(() async => image = await getDockerImage()); - - late DockerContainer container; - late TestNextcloudClient client; - setUp(() async { - container = await getDockerContainer(image); - client = await getTestClient(container); - }); - tearDown(() => container.destroy()); - - test('List directory', () async { - final responses = (await client.webdav.propfind( - Uri(path: '/'), - prop: WebDavPropWithoutValues.fromBools( - nchaspreview: true, - davgetcontenttype: true, - davgetlastmodified: true, - ocsize: true, - ), - )) - .responses; - expect(responses, hasLength(10)); - final props = - responses.singleWhere((final response) => response.href!.endsWith('/Nextcloud.png')).propstats.first.prop; - expect(props.nchaspreview, isTrue); - expect(props.davgetcontenttype, 'image/png'); - expect(webdavDateFormat.parseUtc(props.davgetlastmodified!).isBefore(DateTime.now()), isTrue); - expect(props.ocsize, 50598); - }); - - test('List directory recursively', () async { - final responses = (await client.webdav.propfind( - Uri(path: '/'), - depth: WebDavDepth.infinity, - )) - .responses; - expect(responses, hasLength(48)); - }); - - test('Get file props', () async { - final response = (await client.webdav.propfind( - Uri(path: 'Nextcloud.png'), - prop: WebDavPropWithoutValues.fromBools( - davgetlastmodified: true, - davgetetag: true, - davgetcontenttype: true, - davgetcontentlength: true, - davresourcetype: true, - ocid: true, - ocfileid: true, - ocfavorite: true, - occommentshref: true, - occommentscount: true, - occommentsunread: true, - ocdownloadurl: true, - ocownerid: true, - ocownerdisplayname: true, - ocsize: true, - ocpermissions: true, - ncnote: true, - ncdatafingerprint: true, - nchaspreview: true, - ncmounttype: true, - ncisencrypted: true, - ncmetadataetag: true, - ncuploadtime: true, - nccreationtime: true, - ncrichworkspace: true, - ocssharepermissions: true, - ocmsharepermissions: true, - ), - )) - .toWebDavFiles() - .single; - - expect(response.path, '/Nextcloud.png'); - expect(response.id, isNotEmpty); - expect(response.fileId, isNotEmpty); - expect(response.isCollection, isFalse); - expect(response.mimeType, 'image/png'); - expect(response.etag, isNotEmpty); - expect(response.size, 50598); - expect(response.ownerId, 'user1'); - expect(response.ownerDisplay, 'User One'); - expect(response.lastModified!.isBefore(DateTime.now()), isTrue); - expect(response.isDirectory, isFalse); - expect(response.uploadedDate, DateTime.utc(1970)); - expect(response.createdDate, DateTime.utc(1970)); - expect(response.favorite, isFalse); - expect(response.hasPreview, isTrue); - expect(response.name, 'Nextcloud.png'); - expect(response.isDirectory, isFalse); - - expect(webdavDateFormat.parseUtc(response.props.davgetlastmodified!).isBefore(DateTime.now()), isTrue); - expect(response.props.davgetetag, isNotEmpty); - expect(response.props.davgetcontenttype, 'image/png'); - expect(response.props.davgetcontentlength, 50598); - expect(response.props.davresourcetype!.collection, isNull); - expect(response.props.ocid, isNotEmpty); - expect(response.props.ocfileid, isNotEmpty); - expect(response.props.ocfavorite, 0); - expect(response.props.occommentshref, isNotEmpty); - expect(response.props.occommentscount, 0); - expect(response.props.occommentsunread, 0); - expect(response.props.ocdownloadurl, isNull); - expect(response.props.ocownerid, 'user1'); - expect(response.props.ocownerdisplayname, 'User One'); - expect(response.props.ocsize, 50598); - expect(response.props.ocpermissions, 'RGDNVW'); - expect(response.props.ncnote, isNull); - expect(response.props.ncdatafingerprint, isNull); - expect(response.props.nchaspreview, isTrue); - expect(response.props.ncmounttype, isNull); - expect(response.props.ncisencrypted, isNull); - expect(response.props.ncmetadataetag, isNull); - expect(response.props.ncuploadtime, 0); - expect(response.props.nccreationtime, 0); - expect(response.props.ncrichworkspace, isNull); - expect(response.props.ocssharepermissions, 19); - expect(json.decode(response.props.ocmsharepermissions!), ['share', 'read', 'write']); - }); - - test('Get directory props', () async { - final data = utf8.encode('test') as Uint8List; - await client.webdav.mkcol(Uri(path: 'test')); - await client.webdav.put(data, Uri(path: 'test/test.txt')); - - final response = (await client.webdav.propfind( - Uri(path: 'test'), - prop: WebDavPropWithoutValues.fromBools( - davgetcontenttype: true, - davgetlastmodified: true, - davresourcetype: true, - ocsize: true, - ), - depth: WebDavDepth.zero, - )) - .toWebDavFiles() - .single; - - expect(response.path, '/test/'); - expect(response.isCollection, isTrue); - expect(response.mimeType, isNull); - expect(response.size, data.lengthInBytes); - expectDateInReasonableTimeRange(response.lastModified!, DateTime.now()); - expect(response.name, 'test'); - expect(response.isDirectory, isTrue); - - expect(response.props.davgetcontenttype, isNull); - expectDateInReasonableTimeRange(webdavDateFormat.parseUtc(response.props.davgetlastmodified!), DateTime.now()); - expect(response.props.davresourcetype!.collection, isNotNull); - expect(response.props.ocsize, data.lengthInBytes); - }); - - test('Filter files', () async { - final response = await client.webdav.put(utf8.encode('test') as Uint8List, Uri(path: 'test.txt')); - final id = response.headers['oc-fileid']!.first; - await client.webdav.proppatch( - Uri(path: 'test.txt'), - set: WebDavProp( - ocfavorite: 1, - ), - ); - - final responses = (await client.webdav.report( - Uri(path: '/'), - WebDavOcFilterRules( - ocfavorite: 1, - ), - prop: WebDavPropWithoutValues.fromBools( - ocid: true, - ocfavorite: true, - ), - )) - .responses; - expect(responses, hasLength(1)); - final props = - responses.singleWhere((final response) => response.href!.endsWith('/test.txt')).propstats.first.prop; - expect(props.ocid, id); - expect(props.ocfavorite, 1); - }); - - test('Set properties', () async { - final lastModifiedDate = DateTime.utc(1972, 3); - final createdDate = DateTime.utc(1971, 2); - final uploadTime = DateTime.now(); - - await client.webdav.put( - utf8.encode('test') as Uint8List, - Uri(path: 'test.txt'), - lastModified: lastModifiedDate, - created: createdDate, - ); - - final updated = await client.webdav.proppatch( - Uri(path: 'test.txt'), - set: WebDavProp( - ocfavorite: 1, - ), - ); - expect(updated, isTrue); - - final props = (await client.webdav.propfind( - Uri(path: 'test.txt'), - prop: WebDavPropWithoutValues.fromBools( - ocfavorite: true, - davgetlastmodified: true, - nccreationtime: true, - ncuploadtime: true, - ), - )) - .responses - .single - .propstats - .first - .prop; - expect(props.ocfavorite, 1); - expect(webdavDateFormat.parseUtc(props.davgetlastmodified!), lastModifiedDate); - expect(DateTime.fromMillisecondsSinceEpoch(props.nccreationtime! * 1000).isAtSameMomentAs(createdDate), isTrue); - expectDateInReasonableTimeRange(DateTime.fromMillisecondsSinceEpoch(props.ncuploadtime! * 1000), uploadTime); - }); - - test('Remove properties', () async { - await client.webdav.put(utf8.encode('test') as Uint8List, Uri(path: 'test.txt')); - - var updated = await client.webdav.proppatch( - Uri(path: 'test.txt'), - set: WebDavProp( - ocfavorite: 1, - ), - ); - expect(updated, isTrue); - - var props = (await client.webdav.propfind( - Uri(path: 'test.txt'), - prop: WebDavPropWithoutValues.fromBools( - ocfavorite: true, - nccreationtime: true, - ncuploadtime: true, - ), - )) - .responses - .single - .propstats - .first - .prop; - expect(props.ocfavorite, 1); - - updated = await client.webdav.proppatch( - Uri(path: 'test.txt'), - remove: WebDavPropWithoutValues.fromBools( - ocfavorite: true, - ), - ); - expect(updated, isFalse); - - props = (await client.webdav.propfind( - Uri(path: 'test.txt'), - prop: WebDavPropWithoutValues.fromBools( - ocfavorite: true, - ), - )) - .responses - .single - .propstats - .first - .prop; - expect(props.ocfavorite, 0); - }); - - test('Upload and download file', () async { - final destinationDir = Directory.systemTemp.createTempSync(); - final destination = File('${destinationDir.path}/test.png'); - final source = File('test/files/test.png'); - final progressValues = []; - - await client.webdav.putFile( - source, - source.statSync(), - Uri(path: 'test.png'), - onProgress: progressValues.add, - ); - await client.webdav.getFile( - Uri(path: 'test.png'), - destination, - onProgress: progressValues.add, - ); - expect(progressValues, containsAll([1.0, 1.0])); - expect(destination.readAsBytesSync(), source.readAsBytesSync()); - - destinationDir.deleteSync(recursive: true); - }); - - group('litmus', () { - group('basic', () { - test('options', () async { - final options = await client.webdav.options(); - expect(options.capabilities, contains('1')); - expect(options.capabilities, contains('3')); - // Nextcloud only contains a fake plugin for Class 2 support: https://github.com/nextcloud/server/blob/master/apps/dav/lib/Connector/Sabre/FakeLockerPlugin.php - // It does not actually support locking and is only there for compatibility reasons. - expect(options.capabilities, isNot(contains('2'))); - }); + group( + 'webdav', + () { + late DockerImage image; + setUpAll(() async => image = await getDockerImage()); - for (final (name, path) in [ - ('put_get', 'res'), - ('put_get_utf8_segment', 'res-%e2%82%ac'), - ]) { - test(name, () async { - final content = utf8.encode('This is a test file') as Uint8List; + late DockerContainer container; + late TestNextcloudClient client; - final response = await client.webdav.put(content, Uri(path: path)); - expect(response.statusCode, 201); + setUp(() async { + container = await getDockerContainer(image); + client = await getTestClient(container); + }); + tearDown(() => container.destroy()); + + test('List directory', () async { + final responses = (await client.webdav.propfind( + Uri(path: '/'), + prop: WebDavPropWithoutValues.fromBools( + nchaspreview: true, + davgetcontenttype: true, + davgetlastmodified: true, + ocsize: true, + ), + )) + .responses; + expect(responses, hasLength(10)); + final props = + responses.singleWhere((final response) => response.href!.endsWith('/Nextcloud.png')).propstats.first.prop; + expect(props.nchaspreview, isTrue); + expect(props.davgetcontenttype, 'image/png'); + expect(webdavDateFormat.parseUtc(props.davgetlastmodified!).isBefore(DateTime.now()), isTrue); + expect(props.ocsize, 50598); + }); - final downloadedContent = await client.webdav.get(Uri(path: path)); - expect(downloadedContent, equals(content)); - }); - } - - test('put_no_parent', () async { - expect( - () => client.webdav.put(Uint8List(0), Uri(path: '409me/noparent.txt')), - // https://github.com/nextcloud/server/issues/39625 - throwsA(predicate((final e) => e.statusCode == 409)), - ); - }); + test('List directory recursively', () async { + final responses = (await client.webdav.propfind( + Uri(path: '/'), + depth: WebDavDepth.infinity, + )) + .responses; + expect(responses, hasLength(48)); + }); - test('delete', () async { - await client.webdav.put(Uint8List(0), Uri(path: 'test.txt')); + test('Get file props', () async { + final response = (await client.webdav.propfind( + Uri(path: 'Nextcloud.png'), + prop: WebDavPropWithoutValues.fromBools( + davgetlastmodified: true, + davgetetag: true, + davgetcontenttype: true, + davgetcontentlength: true, + davresourcetype: true, + ocid: true, + ocfileid: true, + ocfavorite: true, + occommentshref: true, + occommentscount: true, + occommentsunread: true, + ocdownloadurl: true, + ocownerid: true, + ocownerdisplayname: true, + ocsize: true, + ocpermissions: true, + ncnote: true, + ncdatafingerprint: true, + nchaspreview: true, + ncmounttype: true, + ncisencrypted: true, + ncmetadataetag: true, + ncuploadtime: true, + nccreationtime: true, + ncrichworkspace: true, + ocssharepermissions: true, + ocmsharepermissions: true, + ), + )) + .toWebDavFiles() + .single; + + expect(response.path, '/Nextcloud.png'); + expect(response.id, isNotEmpty); + expect(response.fileId, isNotEmpty); + expect(response.isCollection, isFalse); + expect(response.mimeType, 'image/png'); + expect(response.etag, isNotEmpty); + expect(response.size, 50598); + expect(response.ownerId, 'user1'); + expect(response.ownerDisplay, 'User One'); + expect(response.lastModified!.isBefore(DateTime.now()), isTrue); + expect(response.isDirectory, isFalse); + expect(response.uploadedDate, DateTime.utc(1970)); + expect(response.createdDate, DateTime.utc(1970)); + expect(response.favorite, isFalse); + expect(response.hasPreview, isTrue); + expect(response.name, 'Nextcloud.png'); + expect(response.isDirectory, isFalse); + + expect(webdavDateFormat.parseUtc(response.props.davgetlastmodified!).isBefore(DateTime.now()), isTrue); + expect(response.props.davgetetag, isNotEmpty); + expect(response.props.davgetcontenttype, 'image/png'); + expect(response.props.davgetcontentlength, 50598); + expect(response.props.davresourcetype!.collection, isNull); + expect(response.props.ocid, isNotEmpty); + expect(response.props.ocfileid, isNotEmpty); + expect(response.props.ocfavorite, 0); + expect(response.props.occommentshref, isNotEmpty); + expect(response.props.occommentscount, 0); + expect(response.props.occommentsunread, 0); + expect(response.props.ocdownloadurl, isNull); + expect(response.props.ocownerid, 'user1'); + expect(response.props.ocownerdisplayname, 'User One'); + expect(response.props.ocsize, 50598); + expect(response.props.ocpermissions, 'RGDNVW'); + expect(response.props.ncnote, isNull); + expect(response.props.ncdatafingerprint, isNull); + expect(response.props.nchaspreview, isTrue); + expect(response.props.ncmounttype, isNull); + expect(response.props.ncisencrypted, isNull); + expect(response.props.ncmetadataetag, isNull); + expect(response.props.ncuploadtime, 0); + expect(response.props.nccreationtime, 0); + expect(response.props.ncrichworkspace, isNull); + expect(response.props.ocssharepermissions, 19); + expect(json.decode(response.props.ocmsharepermissions!), ['share', 'read', 'write']); + }); - final response = await client.webdav.delete(Uri(path: 'test.txt')); - expect(response.statusCode, 204); - }); + test('Get directory props', () async { + final data = utf8.encode('test') as Uint8List; + await client.webdav.mkcol(Uri(path: 'test')); + await client.webdav.put(data, Uri(path: 'test/test.txt')); + + final response = (await client.webdav.propfind( + Uri(path: 'test'), + prop: WebDavPropWithoutValues.fromBools( + davgetcontenttype: true, + davgetlastmodified: true, + davresourcetype: true, + ocsize: true, + ), + depth: WebDavDepth.zero, + )) + .toWebDavFiles() + .single; + + expect(response.path, '/test/'); + expect(response.isCollection, isTrue); + expect(response.mimeType, isNull); + expect(response.size, data.lengthInBytes); + expectDateInReasonableTimeRange(response.lastModified!, DateTime.now()); + expect(response.name, 'test'); + expect(response.isDirectory, isTrue); + + expect(response.props.davgetcontenttype, isNull); + expectDateInReasonableTimeRange(webdavDateFormat.parseUtc(response.props.davgetlastmodified!), DateTime.now()); + expect(response.props.davresourcetype!.collection, isNotNull); + expect(response.props.ocsize, data.lengthInBytes); + }); - test('delete_null', () async { - expect( - () => client.webdav.delete(Uri(path: 'test.txt')), - throwsA(predicate((final e) => e.statusCode == 404)), - ); - }); + test('Filter files', () async { + final response = await client.webdav.put(utf8.encode('test') as Uint8List, Uri(path: 'test.txt')); + final id = response.headers['oc-fileid']!.first; + await client.webdav.proppatch( + Uri(path: 'test.txt'), + set: WebDavProp( + ocfavorite: 1, + ), + ); + + final responses = (await client.webdav.report( + Uri(path: '/'), + WebDavOcFilterRules( + ocfavorite: 1, + ), + prop: WebDavPropWithoutValues.fromBools( + ocid: true, + ocfavorite: true, + ), + )) + .responses; + expect(responses, hasLength(1)); + final props = + responses.singleWhere((final response) => response.href!.endsWith('/test.txt')).propstats.first.prop; + expect(props.ocid, id); + expect(props.ocfavorite, 1); + }); - // delete_fragment: This test is not applicable because the fragment is already removed on the client side + test('Set properties', () async { + final lastModifiedDate = DateTime.utc(1972, 3); + final createdDate = DateTime.utc(1971, 2); + final uploadTime = DateTime.now(); + + await client.webdav.put( + utf8.encode('test') as Uint8List, + Uri(path: 'test.txt'), + lastModified: lastModifiedDate, + created: createdDate, + ); + + final updated = await client.webdav.proppatch( + Uri(path: 'test.txt'), + set: WebDavProp( + ocfavorite: 1, + ), + ); + expect(updated, isTrue); + + final props = (await client.webdav.propfind( + Uri(path: 'test.txt'), + prop: WebDavPropWithoutValues.fromBools( + ocfavorite: true, + davgetlastmodified: true, + nccreationtime: true, + ncuploadtime: true, + ), + )) + .responses + .single + .propstats + .first + .prop; + expect(props.ocfavorite, 1); + expect(webdavDateFormat.parseUtc(props.davgetlastmodified!), lastModifiedDate); + expect(DateTime.fromMillisecondsSinceEpoch(props.nccreationtime! * 1000).isAtSameMomentAs(createdDate), isTrue); + expectDateInReasonableTimeRange(DateTime.fromMillisecondsSinceEpoch(props.ncuploadtime! * 1000), uploadTime); + }); - test('mkcol', () async { - final response = await client.webdav.mkcol(Uri(path: 'test')); - expect(response.statusCode, 201); - }); + test('Remove properties', () async { + await client.webdav.put(utf8.encode('test') as Uint8List, Uri(path: 'test.txt')); + + var updated = await client.webdav.proppatch( + Uri(path: 'test.txt'), + set: WebDavProp( + ocfavorite: 1, + ), + ); + expect(updated, isTrue); + + var props = (await client.webdav.propfind( + Uri(path: 'test.txt'), + prop: WebDavPropWithoutValues.fromBools( + ocfavorite: true, + nccreationtime: true, + ncuploadtime: true, + ), + )) + .responses + .single + .propstats + .first + .prop; + expect(props.ocfavorite, 1); + + updated = await client.webdav.proppatch( + Uri(path: 'test.txt'), + remove: WebDavPropWithoutValues.fromBools( + ocfavorite: true, + ), + ); + expect(updated, isFalse); + + props = (await client.webdav.propfind( + Uri(path: 'test.txt'), + prop: WebDavPropWithoutValues.fromBools( + ocfavorite: true, + ), + )) + .responses + .single + .propstats + .first + .prop; + expect(props.ocfavorite, 0); + }); - test('mkcol_again', () async { - await client.webdav.mkcol(Uri(path: 'test')); + test('Upload and download file', () async { + final destinationDir = Directory.systemTemp.createTempSync(); + final destination = File('${destinationDir.path}/test.png'); + final source = File('test/files/test.png'); + final progressValues = []; + + await client.webdav.putFile( + source, + source.statSync(), + Uri(path: 'test.png'), + onProgress: progressValues.add, + ); + await client.webdav.getFile( + Uri(path: 'test.png'), + destination, + onProgress: progressValues.add, + ); + expect(progressValues, containsAll([1.0, 1.0])); + expect(destination.readAsBytesSync(), source.readAsBytesSync()); + + destinationDir.deleteSync(recursive: true); + }); - expect( - () => client.webdav.mkcol(Uri(path: 'test')), - throwsA(predicate((final e) => e.statusCode == 405)), - ); - }); + group('litmus', () { + group('basic', () { + test('options', () async { + final options = await client.webdav.options(); + expect(options.capabilities, contains('1')); + expect(options.capabilities, contains('3')); + // Nextcloud only contains a fake plugin for Class 2 support: https://github.com/nextcloud/server/blob/master/apps/dav/lib/Connector/Sabre/FakeLockerPlugin.php + // It does not actually support locking and is only there for compatibility reasons. + expect(options.capabilities, isNot(contains('2'))); + }); - test('delete_coll', () async { - var response = await client.webdav.mkcol(Uri(path: 'test')); + for (final (name, path) in [ + ('put_get', 'res'), + ('put_get_utf8_segment', 'res-%e2%82%ac'), + ]) { + test(name, () async { + final content = utf8.encode('This is a test file') as Uint8List; - response = await client.webdav.delete(Uri(path: 'test')); - expect(response.statusCode, 204); - }); + final response = await client.webdav.put(content, Uri(path: path)); + expect(response.statusCode, 201); - test('mkcol_no_parent', () async { - expect( - () => client.webdav.mkcol(Uri(path: '409me/noparent')), - throwsA(predicate((final e) => e.statusCode == 409)), - ); - }); + final downloadedContent = await client.webdav.get(Uri(path: path)); + expect(downloadedContent, equals(content)); + }); + } - // mkcol_with_body: This test is not applicable because we only write valid request bodies - }); + test('put_no_parent', () async { + expect( + () => client.webdav.put(Uint8List(0), Uri(path: '409me/noparent.txt')), + // https://github.com/nextcloud/server/issues/39625 + throwsA(predicate((final e) => e.statusCode == 409)), + ); + }); - group('copymove', () { - test('copy_simple', () async { - await client.webdav.mkcol(Uri(path: 'src')); + test('delete', () async { + await client.webdav.put(Uint8List(0), Uri(path: 'test.txt')); - final response = await client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst')); - expect(response.statusCode, 201); - }); + final response = await client.webdav.delete(Uri(path: 'test.txt')); + expect(response.statusCode, 204); + }); + + test('delete_null', () async { + expect( + () => client.webdav.delete(Uri(path: 'test.txt')), + throwsA(predicate((final e) => e.statusCode == 404)), + ); + }); - test('copy_overwrite', () async { - await client.webdav.mkcol(Uri(path: 'src')); - await client.webdav.mkcol(Uri(path: 'dst')); + // delete_fragment: This test is not applicable because the fragment is already removed on the client side - expect( - () => client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst')), - throwsA(predicate((final e) => e.statusCode == 412)), - ); + test('mkcol', () async { + final response = await client.webdav.mkcol(Uri(path: 'test')); + expect(response.statusCode, 201); + }); - final response = await client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst'), overwrite: true); - expect(response.statusCode, 204); - }); + test('mkcol_again', () async { + await client.webdav.mkcol(Uri(path: 'test')); - test('copy_nodestcoll', () async { - await client.webdav.mkcol(Uri(path: 'src')); + expect( + () => client.webdav.mkcol(Uri(path: 'test')), + throwsA(predicate((final e) => e.statusCode == 405)), + ); + }); + + test('delete_coll', () async { + var response = await client.webdav.mkcol(Uri(path: 'test')); + + response = await client.webdav.delete(Uri(path: 'test')); + expect(response.statusCode, 204); + }); + + test('mkcol_no_parent', () async { + expect( + () => client.webdav.mkcol(Uri(path: '409me/noparent')), + throwsA(predicate((final e) => e.statusCode == 409)), + ); + }); - expect( - () => client.webdav.copy(Uri(path: 'src'), Uri(path: 'nonesuch/dst')), - throwsA(predicate((final e) => e.statusCode == 409)), - ); + // mkcol_with_body: This test is not applicable because we only write valid request bodies }); - test('copy_coll', () async { - await client.webdav.mkcol(Uri(path: 'src')); - await client.webdav.mkcol(Uri(path: 'src/sub')); - for (var i = 0; i < 10; i++) { - await client.webdav.put(Uint8List(0), Uri(path: 'src/$i.txt')); - } - await client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst1')); - await client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst2')); + group('copymove', () { + test('copy_simple', () async { + await client.webdav.mkcol(Uri(path: 'src')); - expect( - () => client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst1')), - throwsA(predicate((final e) => e.statusCode == 412)), - ); + final response = await client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst')); + expect(response.statusCode, 201); + }); - var response = await client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst2'), overwrite: true); - expect(response.statusCode, 204); + test('copy_overwrite', () async { + await client.webdav.mkcol(Uri(path: 'src')); + await client.webdav.mkcol(Uri(path: 'dst')); - for (var i = 0; i < 10; i++) { - response = await client.webdav.delete(Uri(path: 'dst1/$i.txt')); + expect( + () => client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst')), + throwsA(predicate((final e) => e.statusCode == 412)), + ); + + final response = await client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst'), overwrite: true); expect(response.statusCode, 204); - } + }); - response = await client.webdav.delete(Uri(path: 'dst1/sub')); - expect(response.statusCode, 204); + test('copy_nodestcoll', () async { + await client.webdav.mkcol(Uri(path: 'src')); - response = await client.webdav.delete(Uri(path: 'dst2')); - expect(response.statusCode, 204); - }); + expect( + () => client.webdav.copy(Uri(path: 'src'), Uri(path: 'nonesuch/dst')), + throwsA(predicate((final e) => e.statusCode == 409)), + ); + }); - // copy_shallow: Does not work on litmus, let's wait for https://github.com/nextcloud/server/issues/39627 + test('copy_coll', () async { + await client.webdav.mkcol(Uri(path: 'src')); + await client.webdav.mkcol(Uri(path: 'src/sub')); + for (var i = 0; i < 10; i++) { + await client.webdav.put(Uint8List(0), Uri(path: 'src/$i.txt')); + } + await client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst1')); + await client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst2')); + + expect( + () => client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst1')), + throwsA(predicate((final e) => e.statusCode == 412)), + ); + + var response = await client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst2'), overwrite: true); + expect(response.statusCode, 204); - test('move', () async { - await client.webdav.put(Uint8List(0), Uri(path: 'src1.txt')); - await client.webdav.put(Uint8List(0), Uri(path: 'src2.txt')); - await client.webdav.mkcol(Uri(path: 'coll')); + for (var i = 0; i < 10; i++) { + response = await client.webdav.delete(Uri(path: 'dst1/$i.txt')); + expect(response.statusCode, 204); + } - var response = await client.webdav.move(Uri(path: 'src1.txt'), Uri(path: 'dst.txt')); - expect(response.statusCode, 201); + response = await client.webdav.delete(Uri(path: 'dst1/sub')); + expect(response.statusCode, 204); - expect( - () => client.webdav.move(Uri(path: 'src2.txt'), Uri(path: 'dst.txt')), - throwsA(predicate((final e) => e.statusCode == 412)), - ); + response = await client.webdav.delete(Uri(path: 'dst2')); + expect(response.statusCode, 204); + }); - response = await client.webdav.move(Uri(path: 'src2.txt'), Uri(path: 'dst.txt'), overwrite: true); - expect(response.statusCode, 204); - }); + // copy_shallow: Does not work on litmus, let's wait for https://github.com/nextcloud/server/issues/39627 - test('move_coll', () async { - await client.webdav.mkcol(Uri(path: 'src')); - await client.webdav.mkcol(Uri(path: 'src/sub')); - for (var i = 0; i < 10; i++) { - await client.webdav.put(Uint8List(0), Uri(path: 'src/$i.txt')); - } - await client.webdav.put(Uint8List(0), Uri(path: 'noncoll')); - await client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst2')); - await client.webdav.move(Uri(path: 'src'), Uri(path: 'dst1')); + test('move', () async { + await client.webdav.put(Uint8List(0), Uri(path: 'src1.txt')); + await client.webdav.put(Uint8List(0), Uri(path: 'src2.txt')); + await client.webdav.mkcol(Uri(path: 'coll')); - expect( - () => client.webdav.move(Uri(path: 'dst1'), Uri(path: 'dst2')), - throwsA(predicate((final e) => e.statusCode == 412)), - ); + var response = await client.webdav.move(Uri(path: 'src1.txt'), Uri(path: 'dst.txt')); + expect(response.statusCode, 201); - await client.webdav.move(Uri(path: 'dst2'), Uri(path: 'dst1'), overwrite: true); - await client.webdav.copy(Uri(path: 'dst1'), Uri(path: 'dst2')); + expect( + () => client.webdav.move(Uri(path: 'src2.txt'), Uri(path: 'dst.txt')), + throwsA(predicate((final e) => e.statusCode == 412)), + ); - for (var i = 0; i < 10; i++) { - final response = await client.webdav.delete(Uri(path: 'dst1/$i.txt')); + response = await client.webdav.move(Uri(path: 'src2.txt'), Uri(path: 'dst.txt'), overwrite: true); expect(response.statusCode, 204); - } + }); - final response = await client.webdav.delete(Uri(path: 'dst1/sub')); - expect(response.statusCode, 204); + test('move_coll', () async { + await client.webdav.mkcol(Uri(path: 'src')); + await client.webdav.mkcol(Uri(path: 'src/sub')); + for (var i = 0; i < 10; i++) { + await client.webdav.put(Uint8List(0), Uri(path: 'src/$i.txt')); + } + await client.webdav.put(Uint8List(0), Uri(path: 'noncoll')); + await client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst2')); + await client.webdav.move(Uri(path: 'src'), Uri(path: 'dst1')); + + expect( + () => client.webdav.move(Uri(path: 'dst1'), Uri(path: 'dst2')), + throwsA(predicate((final e) => e.statusCode == 412)), + ); + + await client.webdav.move(Uri(path: 'dst2'), Uri(path: 'dst1'), overwrite: true); + await client.webdav.copy(Uri(path: 'dst1'), Uri(path: 'dst2')); + + for (var i = 0; i < 10; i++) { + final response = await client.webdav.delete(Uri(path: 'dst1/$i.txt')); + expect(response.statusCode, 204); + } + + final response = await client.webdav.delete(Uri(path: 'dst1/sub')); + expect(response.statusCode, 204); - expect( - () => client.webdav.move(Uri(path: 'dst2'), Uri(path: 'noncoll')), - throwsA(predicate((final e) => e.statusCode == 412)), - ); + expect( + () => client.webdav.move(Uri(path: 'dst2'), Uri(path: 'noncoll')), + throwsA(predicate((final e) => e.statusCode == 412)), + ); + }); }); - }); - group('largefile', () { - final largefileSize = pow(10, 9).toInt(); // 1GB + group('largefile', () { + final largefileSize = pow(10, 9).toInt(); // 1GB - // large_put: Already covered by large_get + // large_put: Already covered by large_get - test('large_get', () async { - final response = await client.webdav.put(Uint8List(largefileSize), Uri(path: 'test.txt')); - expect(response.statusCode, 201); + test('large_get', () async { + final response = await client.webdav.put(Uint8List(largefileSize), Uri(path: 'test.txt')); + expect(response.statusCode, 201); - final downloadedContent = await client.webdav.get(Uri(path: 'test.txt')); - expect(downloadedContent, hasLength(largefileSize)); + final downloadedContent = await client.webdav.get(Uri(path: 'test.txt')); + expect(downloadedContent, hasLength(largefileSize)); + }); }); - }); - // props: Most of them are either not applicable or hard/impossible to implement because we don't allow just writing any props - }); - }); + // props: Most of them are either not applicable or hard/impossible to implement because we don't allow just writing any props + }); + }, + retry: retryCount, + timeout: timeout, + ); }