diff --git a/packages/nextcloud/lib/src/version_supported.dart b/packages/nextcloud/lib/src/version_supported.dart index 4cf736fb..4a599161 100644 --- a/packages/nextcloud/lib/src/version_supported.dart +++ b/packages/nextcloud/lib/src/version_supported.dart @@ -3,8 +3,14 @@ import 'package:nextcloud/src/api/news.openapi.dart'; import 'package:nextcloud/src/api/notes.openapi.dart'; import 'package:version/version.dart'; -/// Version of core/Server supported -const coreSupportedVersion = 27; +/// Versions of core/Server supported +const coreSupportedVersions = [ + '25.0.10', + '26.0.5', + '27.0.0', + '27.0.1', + '27.0.2', +]; /// API version of the news app supported const newsSupportedVersion = 'v1-3'; @@ -17,16 +23,16 @@ extension CoreVersionSupported on CoreClient { /// Check if the core/Server version is supported by this client /// /// Also returns the supported version number - (bool, int) isSupported(final CoreOcsGetCapabilitiesResponseApplicationJson_Ocs_Data capabilities) => ( - capabilities.version.major == coreSupportedVersion, - coreSupportedVersion, + (bool, List) isSupported(final CoreOcsGetCapabilitiesResponseApplicationJson_Ocs_Data capabilities) => ( + coreSupportedVersions.contains(capabilities.version.string), + coreSupportedVersions, ); } // ignore: public_member_api_docs extension CoreStatusVersionSupported on CoreStatus { /// Check if the core/Server version is supported - bool get isSupported => version.startsWith('$coreSupportedVersion.'); + bool get isSupported => coreSupportedVersions.contains(versionstring); } // ignore: public_member_api_docs diff --git a/packages/nextcloud/test/core_test.dart b/packages/nextcloud/test/core_test.dart index 3e99b608..10d5b9b3 100644 --- a/packages/nextcloud/test/core_test.dart +++ b/packages/nextcloud/test/core_test.dart @@ -4,159 +4,164 @@ 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, - ], + for (final serverVersion in coreSupportedVersions) { + final serverVersionMajor = int.parse(serverVersion.split('.').first); + group( + serverVersion, + () { + group('core', () { + late DockerImage image; + setUpAll(() async => image = await getDockerImage(serverVersion: serverVersion)); + + 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('$serverVersion.')); + expect(status.versionstring, serverVersion); + 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, serverVersionMajor); + expect(capabilities.ocs.data.version.string, serverVersion); + 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: serverVersionMajor < 28, // This test only works on 28+ due to a bug fix with the status ); - 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)), - ); - }); - - 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); - }); - }, - retry: retryCount, - timeout: timeout, - ); + + 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); + }); + }); + }, + retry: retryCount, + timeout: timeout, + ); + } } diff --git a/packages/nextcloud/test/helper.dart b/packages/nextcloud/test/helper.dart index 47d104a3..e0105485 100644 --- a/packages/nextcloud/test/helper.dart +++ b/packages/nextcloud/test/helper.dart @@ -192,8 +192,15 @@ Future getDockerContainer(final DockerImage image, {final bool typedef DockerImage = String; -Future getDockerImage() async { - const dockerImageName = 'nextcloud-neon-dev'; +Future getDockerImage({ + final String? serverVersion, +}) async { + final buildArgs = { + if (serverVersion != null) 'SERVER_VERSION': serverVersion, + }; + + final dockerImageName = + 'nextcloud-neon-test${buildArgs.isNotEmpty ? '-' : ''}${buildArgs.entries.map((final buildArg) => '${buildArg.key.toLowerCase().replaceAll('_', '-')}-${buildArg.value}').join('-')}'; final inputStream = StreamController>(); final process = runExecutableArguments( @@ -202,6 +209,9 @@ Future getDockerImage() async { 'build', '-t', dockerImageName, + ...buildArgs.entries + .map((final buildArg) => ['--build-arg', '${buildArg.key}=${buildArg.value}']) + .expand((final buildArg) => buildArg), '-f', '-', '../../tool', @@ -213,7 +223,7 @@ Future getDockerImage() async { final result = await process; if (result.exitCode != 0) { - throw Exception('Failed to build docker image'); + throw Exception('Failed to build docker image:\n${result.stdout as String}\n${result.stderr as String}'); } return dockerImageName;