diff --git a/packages/neon_framework/lib/src/pages/home.dart b/packages/neon_framework/lib/src/pages/home.dart index cb4b7abf52f..3a70a888f58 100644 --- a/packages/neon_framework/lib/src/pages/home.dart +++ b/packages/neon_framework/lib/src/pages/home.dart @@ -55,7 +55,7 @@ class _HomePageState extends State { final versionCheck = entry.value; final appName = l10n.appImplementationName(entry.key); - buffer.writeln('- $appName >=${versionCheck.minimumVersion} <${versionCheck.maximumMajor + 1}.0.0'); + buffer.writeln('- $appName >=${versionCheck.minimumVersion}'); } final message = l10n.errorUnsupportedAppVersions(buffer.toString()); diff --git a/packages/neon_framework/test/apps_bloc_test.dart b/packages/neon_framework/test/apps_bloc_test.dart index b84876b67c6..4f845006be6 100644 --- a/packages/neon_framework/test/apps_bloc_test.dart +++ b/packages/neon_framework/test/apps_bloc_test.dart @@ -329,13 +329,11 @@ void main() { final versionCheckCore = VersionCheck( versions: BuiltList([Version(0, 0, 0)]), minimumVersion: core.minVersion, - maximumMajor: core.maxMajor, ); final versionCheck1 = VersionCheck( versions: BuiltList([Version(2, 0, 0)]), minimumVersion: Version(2, 0, 0), - maximumMajor: 2, ); final appImplementation1 = MockAppImplementation(); @@ -346,7 +344,6 @@ void main() { final versionCheck2 = VersionCheck( versions: BuiltList([Version(1, 0, 0)]), minimumVersion: Version(2, 0, 0), - maximumMajor: 2, ); final appImplementation2 = MockAppImplementation(); diff --git a/packages/nextcloud/README.md b/packages/nextcloud/README.md index 9119236e6ab..3f09d2abacf 100644 --- a/packages/nextcloud/README.md +++ b/packages/nextcloud/README.md @@ -46,11 +46,6 @@ They can be accessed using getters on the `NextcloudClient`. For an example checkout the [example](https://github.com/nextcloud/neon/blob/main/packages/nextcloud/example/example.dart). -## Development - -Except for WebDAV all client code is generated using OpenAPI specifications which can be found in the `lib/src/api/` folder. -These OpenAPI specifications are [generated](https://github.com/nextcloud/openapi-extractor) from the PHP source code. - ## Compatibility/Support policy | Component | Supported versions (1) | diff --git a/packages/nextcloud/doc/development.md b/packages/nextcloud/doc/development.md new file mode 100644 index 00000000000..965dcae3a32 --- /dev/null +++ b/packages/nextcloud/doc/development.md @@ -0,0 +1,41 @@ +# Development + +Except for WebDAV all client code is generated using OpenAPI specifications which can be found in the `lib/src/api/` folder. +These OpenAPI specifications are [generated](https://github.com/nextcloud/openapi-extractor) from the PHP source code. + +## Checking test do not interfere with each other + +All tests should be standalone and never affect any other tests. +With enough caution this is feasible to implement, but to verify the tests do not depend on the order of execution they should be run multiple times with different seeds: + +```sh +for i in {0..4}; do fvm flutter test --test-randomize-ordering-seed $i; done +``` + +## Testing against development versions + +Make sure to have the following apps in your server development setup and the appropriate versions you want to test checked out: + +- https://codeberg.org/NextPush/uppush +- https://framagit.org/framasoft/nextcloud/drop_account +- https://github.com/nextcloud/cookbook +- https://github.com/nextcloud/news +- https://github.com/nextcloud/notes +- https://github.com/nextcloud/notifications +- https://github.com/nextcloud/password_policy +- https://github.com/nextcloud/spreed +- https://github.com/nextcloud/tables +- https://github.com/nextcloud/text + +To run the tests against development versions follow these steps: + +```sh +cd /path/to/server +/path/to/neon/packages/nextcloud_test/docker/local.sh + +# Open a second terminal +cd /path/to/neon/packages/nextcloud +URL="http://localhost:8080" DIR="/path/to/server" fvm dart test --concurrency=1 +``` + +Forcing the serial execution of all tests is necessary because the used SQLite database can be locked which results in tests blocking and failing other concurrent tests. diff --git a/packages/nextcloud/lib/src/api/cookbook/cookbook_helpers.dart b/packages/nextcloud/lib/src/api/cookbook/cookbook_helpers.dart index 316aa17bfc1..b288e342323 100644 --- a/packages/nextcloud/lib/src/api/cookbook/cookbook_helpers.dart +++ b/packages/nextcloud/lib/src/api/cookbook/cookbook_helpers.dart @@ -29,7 +29,6 @@ extension CookbookVersionCheck on cookbook.$Client { return VersionCheck( versions: versions, minimumVersion: minAppVersion, - maximumMajor: null, ); } } diff --git a/packages/nextcloud/lib/src/api/core/core_helpers.dart b/packages/nextcloud/lib/src/api/core/core_helpers.dart index b394cd9e6f1..a7c36a7a3dc 100644 --- a/packages/nextcloud/lib/src/api/core/core_helpers.dart +++ b/packages/nextcloud/lib/src/api/core/core_helpers.dart @@ -8,9 +8,6 @@ import 'package:version/version.dart'; /// Minimum version of core/Server supported final minVersion = Version(28, 0, 0); -/// Maximum major of core/Server supported -const maxMajor = 29; - /// Checks whether the server [version] is a dev, beta or RC version. bool _isDevelopmentServerVersion(String version) { return version.contains('dev') || version.contains('beta') || version.contains('RC'); @@ -29,7 +26,6 @@ extension CoreVersionCheck on core.$Client { return VersionCheck( versions: BuiltList([version]), minimumVersion: minVersion, - maximumMajor: maxMajor, isSupportedOverride: _isDevelopmentServerVersion(capabilities.version.string) ? true : null, ); } @@ -40,7 +36,6 @@ extension CoreStatusVersionCheck on core.Status { VersionCheck get versionCheck => VersionCheck( versions: BuiltList([Version.parse(version)]), minimumVersion: minVersion, - maximumMajor: maxMajor, isSupportedOverride: _isDevelopmentServerVersion(versionstring) ? true : null, ); } diff --git a/packages/nextcloud/lib/src/api/news/news_helpers.dart b/packages/nextcloud/lib/src/api/news/news_helpers.dart index efd19ec6e0e..7c17993be97 100644 --- a/packages/nextcloud/lib/src/api/news/news_helpers.dart +++ b/packages/nextcloud/lib/src/api/news/news_helpers.dart @@ -18,7 +18,6 @@ extension NewsVersionCheck on news.$Client { return VersionCheck( versions: versions?.map((version) => Version.parse(version.substring(1).replaceAll('-', '.'))).toBuiltList(), minimumVersion: minVersion, - maximumMajor: null, ); } } diff --git a/packages/nextcloud/lib/src/api/notes/notes_helpers.dart b/packages/nextcloud/lib/src/api/notes/notes_helpers.dart index f31027b8b06..84b0d174634 100644 --- a/packages/nextcloud/lib/src/api/notes/notes_helpers.dart +++ b/packages/nextcloud/lib/src/api/notes/notes_helpers.dart @@ -17,7 +17,6 @@ extension NotesVersionCheck on notes.$Client { return VersionCheck( versions: versions?.map(Version.parse).toBuiltList(), minimumVersion: minVersion, - maximumMajor: null, ); } } diff --git a/packages/nextcloud/lib/src/api/spreed/spreed_helpers.dart b/packages/nextcloud/lib/src/api/spreed/spreed_helpers.dart index ad4858520df..ca7129b934c 100644 --- a/packages/nextcloud/lib/src/api/spreed/spreed_helpers.dart +++ b/packages/nextcloud/lib/src/api/spreed/spreed_helpers.dart @@ -7,9 +7,6 @@ import 'package:version/version.dart'; /// The minimum version of the spreed app that is supported. final minVersion = Version(18, 0, 0); -/// Maximum major of spreed supported -const maxMajor = 19; - /// Extension for checking whether spreed is supported. extension SpreedVersionCheck on spreed.$Client { /// Checks whether the spreed app installed on the server is supported by this client. @@ -20,7 +17,6 @@ extension SpreedVersionCheck on spreed.$Client { return VersionCheck( versions: version != null ? BuiltList([Version.parse(version)]) : null, minimumVersion: minVersion, - maximumMajor: maxMajor, ); } } diff --git a/packages/nextcloud/lib/src/api/tables/tables_helpers.dart b/packages/nextcloud/lib/src/api/tables/tables_helpers.dart index 74eeefbac8c..a1c991e9d88 100644 --- a/packages/nextcloud/lib/src/api/tables/tables_helpers.dart +++ b/packages/nextcloud/lib/src/api/tables/tables_helpers.dart @@ -18,7 +18,6 @@ extension TablesVersionCheck on tables.$Client { return VersionCheck( versions: version != null ? BuiltList([Version.parse(version)]) : null, minimumVersion: minAppVersion, - maximumMajor: null, ); } } diff --git a/packages/nextcloud/lib/src/models/version_check.dart b/packages/nextcloud/lib/src/models/version_check.dart index d8362f48bc1..11b2888594a 100644 --- a/packages/nextcloud/lib/src/models/version_check.dart +++ b/packages/nextcloud/lib/src/models/version_check.dart @@ -2,19 +2,15 @@ import 'package:built_collection/built_collection.dart'; import 'package:meta/meta.dart'; import 'package:version/version.dart'; -/// Holds the [versions], [minimumVersion] and [maximumMajor] of an app. +/// Holds the [versions], [minimumVersion] of an app. @immutable class VersionCheck { /// Creates a new [VersionCheck]. - /// - /// If the [maximumMajor] is `null` the compatibility of the major of the [minimumVersion] is checked. const VersionCheck({ required this.versions, required this.minimumVersion, - required int? maximumMajor, bool? isSupportedOverride, - }) : _maximumMajor = maximumMajor, - _isSupportedOverride = isSupportedOverride; + }) : _isSupportedOverride = isSupportedOverride; /// Current version of the app. final BuiltList? versions; @@ -22,15 +18,10 @@ class VersionCheck { /// Minimum version of the app. final Version minimumVersion; - final int? _maximumMajor; - - /// Maximum major version of the app. - int get maximumMajor => _maximumMajor ?? minimumVersion.major; - /// Overrides the check if the current version is supported. final bool? _isSupportedOverride; - /// Whether the [versions] is allowed by the [minimumVersion] and [maximumMajor]. + /// Whether the [versions] is allowed by the [minimumVersion]. /// /// If [versions] is `null` or empty it is assumed that the app is supported. /// Only one of the [versions] has to be supported to return `true`. @@ -44,7 +35,7 @@ class VersionCheck { } for (final version in versions!) { - if (version >= minimumVersion && version.major <= maximumMajor) { + if (version >= minimumVersion) { return true; } } @@ -57,14 +48,12 @@ class VersionCheck { other is VersionCheck && other.versions == versions && other.minimumVersion == minimumVersion && - other.maximumMajor == maximumMajor && other._isSupportedOverride == _isSupportedOverride; @override int get hashCode => Object.hashAll([ versions, minimumVersion, - maximumMajor, _isSupportedOverride, ]); } diff --git a/packages/nextcloud/test/cookbook_test.dart b/packages/nextcloud/test/cookbook_test.dart index 5182b033c11..aa61c48ca45 100644 --- a/packages/nextcloud/test/cookbook_test.dart +++ b/packages/nextcloud/test/cookbook_test.dart @@ -6,20 +6,21 @@ import 'package:nextcloud_test/nextcloud_test.dart'; import 'package:test/test.dart'; void main() { + final targetFactory = TestTargetFactory.create(); + presets( + targetFactory, 'cookbook', 'cookbook', (preset) { - late DockerContainer container; + late TestTargetInstance target; late NextcloudClient client; - - setUp(() async { - container = await DockerContainer.create(preset); - client = await TestNextcloudClient.create(container); + setUpAll(() async { + target = await targetFactory.spawn(preset); + client = await target.createClient(); }); - - tearDown(() async { - await container.destroy(); + tearDownAll(() async { + await target.destroy(); }); group('CookbookVersionCheck', () { @@ -115,7 +116,7 @@ void main() { group('recipes', () { test('callImport', () async { - final url = cookbook.UrlBuilder()..url = 'http://localhost/static/recipe.html'; + final url = cookbook.UrlBuilder()..url = '${target.targetURL}/static/recipe.html'; final response = await client.cookbook.recipes.$import($body: url.build()); addTearDown(() async { closeFixture(); diff --git a/packages/nextcloud/test/core_test.dart b/packages/nextcloud/test/core_test.dart index 000db284c96..a4f3defb367 100644 --- a/packages/nextcloud/test/core_test.dart +++ b/packages/nextcloud/test/core_test.dart @@ -7,18 +7,21 @@ import 'package:nextcloud_test/nextcloud_test.dart'; import 'package:test/test.dart'; void main() { + final targetFactory = TestTargetFactory.create(); + presets( + targetFactory, 'server', 'core', (preset) { - late DockerContainer container; + late TestTargetInstance target; late NextcloudClient client; setUpAll(() async { - container = await DockerContainer.create(preset); - client = await TestNextcloudClient.create(container); + target = await targetFactory.spawn(preset); + client = await target.createClient(); }); tearDownAll(() async { - await container.destroy(); + await target.destroy(); }); test('Is supported from capabilities', () async { @@ -94,8 +97,6 @@ void main() { containsAll([ 'dashboard', 'files', - 'photos', - 'activity', 'spreed', 'notes', 'tables', @@ -168,7 +169,7 @@ void main() { group('App password', () { test('Delete', () async { // Separate client to not break other tests - final client = await TestNextcloudClient.create(container); + final client = await target.createClient(); await client.core.appPassword.deleteAppPassword(); await expectLater( diff --git a/packages/nextcloud/test/dashboard_test.dart b/packages/nextcloud/test/dashboard_test.dart index 340458866c1..1690c85e9a3 100644 --- a/packages/nextcloud/test/dashboard_test.dart +++ b/packages/nextcloud/test/dashboard_test.dart @@ -4,18 +4,21 @@ import 'package:nextcloud_test/nextcloud_test.dart'; import 'package:test/test.dart'; void main() { + final targetFactory = TestTargetFactory.create(); + presets( + targetFactory, 'server', 'dashboard', (preset) { - late DockerContainer container; + late TestTargetInstance target; late NextcloudClient client; setUpAll(() async { - container = await DockerContainer.create(preset); - client = await TestNextcloudClient.create(container); + target = await targetFactory.spawn(preset); + client = await target.createClient(); }); tearDownAll(() async { - await container.destroy(); + await target.destroy(); }); test('Get widgets', () async { diff --git a/packages/nextcloud/test/fixtures/cookbook/recipes/callimport.regexp b/packages/nextcloud/test/fixtures/cookbook/recipes/callimport.regexp index 8c76767b471..f4af4cee37b 100644 --- a/packages/nextcloud/test/fixtures/cookbook/recipes/callimport.regexp +++ b/packages/nextcloud/test/fixtures/cookbook/recipes/callimport.regexp @@ -2,4 +2,4 @@ POST http://localhost/index\.php/apps/cookbook/api/v1/import accept: application/json authorization: Basic mock content-type: application/json; charset=utf-8 -\{"url":"http://localhost/static/recipe\.html"\} \ No newline at end of file +\{"url":"http://.*/static/recipe\.html"\} \ No newline at end of file diff --git a/packages/nextcloud/test/fixtures/news/add_feed.regexp b/packages/nextcloud/test/fixtures/news/add_feed.regexp index 0cec60895d4..a225ed7c358 100644 --- a/packages/nextcloud/test/fixtures/news/add_feed.regexp +++ b/packages/nextcloud/test/fixtures/news/add_feed.regexp @@ -1,7 +1,7 @@ GET http://localhost/index\.php/apps/news/api/v1-3/feeds accept: application/json authorization: Basic mock -POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2Flocalhost%2Fstatic%2Fwikipedia\.xml +POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2F.*%2Fstatic%2Fwikipedia\.xml accept: application/json authorization: Basic mock GET http://localhost/index\.php/apps/news/api/v1-3/feeds diff --git a/packages/nextcloud/test/fixtures/news/add_feed_to_folder.regexp b/packages/nextcloud/test/fixtures/news/add_feed_to_folder.regexp index b3a5a4f8909..a0c4f11ee0e 100644 --- a/packages/nextcloud/test/fixtures/news/add_feed_to_folder.regexp +++ b/packages/nextcloud/test/fixtures/news/add_feed_to_folder.regexp @@ -1,6 +1,6 @@ POST http://localhost/index\.php/apps/news/api/v1-3/folders\?name=test1 accept: application/json authorization: Basic mock -POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2Flocalhost%2Fstatic%2Fwikipedia\.xml&folderId=[0-9]+ +POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2F.*%2Fstatic%2Fwikipedia\.xml&folderId=[0-9]+ accept: application/json authorization: Basic mock \ No newline at end of file diff --git a/packages/nextcloud/test/fixtures/news/delete_feed.regexp b/packages/nextcloud/test/fixtures/news/delete_feed.regexp index 2a2cfc00598..1d2eb0d0d1d 100644 --- a/packages/nextcloud/test/fixtures/news/delete_feed.regexp +++ b/packages/nextcloud/test/fixtures/news/delete_feed.regexp @@ -1,4 +1,4 @@ -POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2Flocalhost%2Fstatic%2Fwikipedia\.xml +POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2F.*%2Fstatic%2Fwikipedia\.xml accept: application/json authorization: Basic mock DELETE http://localhost/index\.php/apps/news/api/v1-3/feeds/[0-9]+ diff --git a/packages/nextcloud/test/fixtures/news/list_articles.regexp b/packages/nextcloud/test/fixtures/news/list_articles.regexp index ff990b31a97..9b98b67b713 100644 --- a/packages/nextcloud/test/fixtures/news/list_articles.regexp +++ b/packages/nextcloud/test/fixtures/news/list_articles.regexp @@ -1,7 +1,7 @@ GET http://localhost/index\.php/apps/news/api/v1-3/items\?type=3&id=0&getRead=1&batchSize=-1&offset=0&oldestFirst=0 accept: application/json authorization: Basic mock -POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2Flocalhost%2Fstatic%2Fwikipedia\.xml +POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2F.*%2Fstatic%2Fwikipedia\.xml accept: application/json authorization: Basic mock GET http://localhost/index\.php/apps/news/api/v1-3/items\?type=3&id=0&getRead=1&batchSize=-1&offset=0&oldestFirst=0 diff --git a/packages/nextcloud/test/fixtures/news/list_updated_articles.regexp b/packages/nextcloud/test/fixtures/news/list_updated_articles.regexp index 1216d0fab46..948ff2c1659 100644 --- a/packages/nextcloud/test/fixtures/news/list_updated_articles.regexp +++ b/packages/nextcloud/test/fixtures/news/list_updated_articles.regexp @@ -1,10 +1,10 @@ -POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2Flocalhost%2Fstatic%2Fwikipedia\.xml +POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2F.*%2Fstatic%2Fwikipedia\.xml accept: application/json authorization: Basic mock GET http://localhost/index\.php/apps/news/api/v1-3/items\?type=3&id=0&getRead=1&batchSize=-1&offset=0&oldestFirst=0 accept: application/json authorization: Basic mock -POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2Flocalhost%2Fstatic%2Fnasa\.xml +POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2F.*%2Fstatic%2Fnasa\.xml accept: application/json authorization: Basic mock GET http://localhost/index\.php/apps/news/api/v1-3/items\?type=3&id=0&getRead=1&batchSize=-1&offset=0&oldestFirst=0 diff --git a/packages/nextcloud/test/fixtures/news/mark_article_as_read.regexp b/packages/nextcloud/test/fixtures/news/mark_article_as_read.regexp index 14305be4fad..83016b835a6 100644 --- a/packages/nextcloud/test/fixtures/news/mark_article_as_read.regexp +++ b/packages/nextcloud/test/fixtures/news/mark_article_as_read.regexp @@ -1,4 +1,4 @@ -POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2Flocalhost%2Fstatic%2Fwikipedia\.xml +POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2F.*%2Fstatic%2Fwikipedia\.xml accept: application/json authorization: Basic mock GET http://localhost/index\.php/apps/news/api/v1-3/items\?type=6&id=0&getRead=1&batchSize=-1&offset=0&oldestFirst=0 diff --git a/packages/nextcloud/test/fixtures/news/mark_article_as_unread.regexp b/packages/nextcloud/test/fixtures/news/mark_article_as_unread.regexp index a84bf1938ed..a371d5ca861 100644 --- a/packages/nextcloud/test/fixtures/news/mark_article_as_unread.regexp +++ b/packages/nextcloud/test/fixtures/news/mark_article_as_unread.regexp @@ -1,4 +1,4 @@ -POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2Flocalhost%2Fstatic%2Fwikipedia\.xml +POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2F.*%2Fstatic%2Fwikipedia\.xml accept: application/json authorization: Basic mock GET http://localhost/index\.php/apps/news/api/v1-3/items\?type=6&id=0&getRead=1&batchSize=-1&offset=0&oldestFirst=0 diff --git a/packages/nextcloud/test/fixtures/news/mark_feed_as_read.regexp b/packages/nextcloud/test/fixtures/news/mark_feed_as_read.regexp index 0177263bf8c..08792f8e845 100644 --- a/packages/nextcloud/test/fixtures/news/mark_feed_as_read.regexp +++ b/packages/nextcloud/test/fixtures/news/mark_feed_as_read.regexp @@ -1,4 +1,4 @@ -POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2Flocalhost%2Fstatic%2Fwikipedia\.xml +POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2F.*%2Fstatic%2Fwikipedia\.xml accept: application/json authorization: Basic mock GET http://localhost/index\.php/apps/news/api/v1-3/items\?type=6&id=0&getRead=1&batchSize=-1&offset=0&oldestFirst=0 diff --git a/packages/nextcloud/test/fixtures/news/mark_folder_as_read.regexp b/packages/nextcloud/test/fixtures/news/mark_folder_as_read.regexp index 7dd03e9a2e1..5af621b1b06 100644 --- a/packages/nextcloud/test/fixtures/news/mark_folder_as_read.regexp +++ b/packages/nextcloud/test/fixtures/news/mark_folder_as_read.regexp @@ -1,7 +1,7 @@ POST http://localhost/index\.php/apps/news/api/v1-3/folders\?name=test1 accept: application/json authorization: Basic mock -POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2Flocalhost%2Fstatic%2Fwikipedia\.xml&folderId=[0-9]+ +POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2F.*%2Fstatic%2Fwikipedia\.xml&folderId=[0-9]+ accept: application/json authorization: Basic mock GET http://localhost/index\.php/apps/news/api/v1-3/items\?type=6&id=0&getRead=1&batchSize=-1&offset=0&oldestFirst=0 diff --git a/packages/nextcloud/test/fixtures/news/move_feed_to_folder.regexp b/packages/nextcloud/test/fixtures/news/move_feed_to_folder.regexp index 484eb7d1c4a..8c52ff7dc20 100644 --- a/packages/nextcloud/test/fixtures/news/move_feed_to_folder.regexp +++ b/packages/nextcloud/test/fixtures/news/move_feed_to_folder.regexp @@ -1,7 +1,7 @@ POST http://localhost/index\.php/apps/news/api/v1-3/folders\?name=test1 accept: application/json authorization: Basic mock -POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2Flocalhost%2Fstatic%2Fwikipedia\.xml +POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2F.*%2Fstatic%2Fwikipedia\.xml accept: application/json authorization: Basic mock POST http://localhost/index\.php/apps/news/api/v1-3/feeds/[0-9]+/move\?folderId=[0-9]+ diff --git a/packages/nextcloud/test/fixtures/news/rename_feed.regexp b/packages/nextcloud/test/fixtures/news/rename_feed.regexp index 6fd7c826f8c..e27ddcde29a 100644 --- a/packages/nextcloud/test/fixtures/news/rename_feed.regexp +++ b/packages/nextcloud/test/fixtures/news/rename_feed.regexp @@ -1,4 +1,4 @@ -POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2Flocalhost%2Fstatic%2Fwikipedia\.xml +POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2F.*%2Fstatic%2Fwikipedia\.xml accept: application/json authorization: Basic mock POST http://localhost/index\.php/apps/news/api/v1-3/feeds/[0-9]+/rename\?feedTitle=test1 diff --git a/packages/nextcloud/test/fixtures/news/star_article.regexp b/packages/nextcloud/test/fixtures/news/star_article.regexp index 289f34c18aa..0cf3d50ce98 100644 --- a/packages/nextcloud/test/fixtures/news/star_article.regexp +++ b/packages/nextcloud/test/fixtures/news/star_article.regexp @@ -1,4 +1,4 @@ -POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2Flocalhost%2Fstatic%2Fwikipedia\.xml +POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2F.*%2Fstatic%2Fwikipedia\.xml accept: application/json authorization: Basic mock GET http://localhost/index\.php/apps/news/api/v1-3/items\?type=2&id=0&getRead=1&batchSize=-1&offset=0&oldestFirst=0 diff --git a/packages/nextcloud/test/fixtures/news/unstar_article.regexp b/packages/nextcloud/test/fixtures/news/unstar_article.regexp index 758735e912a..b30d5d1dffb 100644 --- a/packages/nextcloud/test/fixtures/news/unstar_article.regexp +++ b/packages/nextcloud/test/fixtures/news/unstar_article.regexp @@ -1,4 +1,4 @@ -POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2Flocalhost%2Fstatic%2Fwikipedia\.xml +POST http://localhost/index\.php/apps/news/api/v1-3/feeds\?url=http%3A%2F%2F.*%2Fstatic%2Fwikipedia\.xml accept: application/json authorization: Basic mock GET http://localhost/index\.php/apps/news/api/v1-3/items\?type=3&id=0&getRead=1&batchSize=-1&offset=0&oldestFirst=0 diff --git a/packages/nextcloud/test/news_test.dart b/packages/nextcloud/test/news_test.dart index fb7c6ea056c..5df6f301833 100644 --- a/packages/nextcloud/test/news_test.dart +++ b/packages/nextcloud/test/news_test.dart @@ -6,18 +6,21 @@ import 'package:nextcloud_test/nextcloud_test.dart'; import 'package:test/test.dart'; void main() { + final targetFactory = TestTargetFactory.create(); + presets( + targetFactory, 'news', 'news', (preset) { - late DockerContainer container; + late TestTargetInstance target; late NextcloudClient client; setUpAll(() async { - container = await DockerContainer.create(preset); - client = await TestNextcloudClient.create(container); + target = await targetFactory.spawn(preset); + client = await target.createClient(); }); tearDownAll(() async { - await container.destroy(); + await target.destroy(); }); tearDown(() async { closeFixture(); @@ -34,12 +37,12 @@ void main() { }); Future> addWikipediaFeed([int? folderID]) async => client.news.addFeed( - url: 'http://localhost/static/wikipedia.xml', + url: '${target.targetURL}/static/wikipedia.xml', folderId: folderID, ); Future> addNasaFeed() async => client.news.addFeed( - url: 'http://localhost/static/nasa.xml', + url: '${target.targetURL}/static/nasa.xml', ); test('Is supported', () async { @@ -65,7 +68,7 @@ void main() { expect(response.body.starredCount, null); expect(response.body.newestItemId, isNotNull); expect(response.body.feeds, hasLength(1)); - expect(response.body.feeds[0].url, 'http://localhost/static/wikipedia.xml'); + expect(response.body.feeds[0].url, '${target.targetURL}/static/wikipedia.xml'); response = await client.news.listFeeds(); expect(response.statusCode, 200); @@ -74,7 +77,7 @@ void main() { expect(response.body.starredCount, 0); expect(response.body.newestItemId, isNotNull); expect(response.body.feeds, hasLength(1)); - expect(response.body.feeds[0].url, 'http://localhost/static/wikipedia.xml'); + expect(response.body.feeds[0].url, '${target.targetURL}/static/wikipedia.xml'); }); test('Delete feed', () async { @@ -423,7 +426,7 @@ void main() { expect(response.body.newestItemId, isNotNull); expect(response.body.feeds, hasLength(1)); expect(response.body.feeds[0].folderId, isPositive); - expect(response.body.feeds[0].url, 'http://localhost/static/wikipedia.xml'); + expect(response.body.feeds[0].url, '${target.targetURL}/static/wikipedia.xml'); }); test('Mark folder as read', () async { diff --git a/packages/nextcloud/test/notes_test.dart b/packages/nextcloud/test/notes_test.dart index b3461d9aefd..8082ac64ea7 100644 --- a/packages/nextcloud/test/notes_test.dart +++ b/packages/nextcloud/test/notes_test.dart @@ -6,18 +6,21 @@ import 'package:nextcloud_test/nextcloud_test.dart'; import 'package:test/test.dart'; void main() { + final targetFactory = TestTargetFactory.create(); + presets( + targetFactory, 'notes', 'notes', (preset) { - late DockerContainer container; + late TestTargetInstance target; late NextcloudClient client; setUpAll(() async { - container = await DockerContainer.create(preset); - client = await TestNextcloudClient.create(container); + target = await targetFactory.spawn(preset); + client = await target.createClient(); }); tearDownAll(() async { - await container.destroy(); + await target.destroy(); }); tearDown(() async { closeFixture(); diff --git a/packages/nextcloud/test/notifications_test.dart b/packages/nextcloud/test/notifications_test.dart index 690f31dfa95..39615f2f41c 100644 --- a/packages/nextcloud/test/notifications_test.dart +++ b/packages/nextcloud/test/notifications_test.dart @@ -8,21 +8,23 @@ import 'package:nextcloud_test/nextcloud_test.dart'; import 'package:test/test.dart'; void main() { + final targetFactory = TestTargetFactory.create(); + presets( + targetFactory, 'server', 'notifications', (preset) { - late DockerContainer container; + late TestTargetInstance target; late NextcloudClient client; setUpAll(() async { - container = await DockerContainer.create(preset); - client = await TestNextcloudClient.create( - container, + target = await targetFactory.spawn(preset); + client = await target.createClient( username: 'admin', ); }); tearDownAll(() async { - await container.destroy(); + await target.destroy(); }); Future sendTestNotification() async { diff --git a/packages/nextcloud/test/provisioning_api_test.dart b/packages/nextcloud/test/provisioning_api_test.dart index c564f124006..5b209977bb4 100644 --- a/packages/nextcloud/test/provisioning_api_test.dart +++ b/packages/nextcloud/test/provisioning_api_test.dart @@ -5,21 +5,23 @@ import 'package:nextcloud_test/nextcloud_test.dart'; import 'package:test/test.dart'; void main() { + final targetFactory = TestTargetFactory.create(); + presets( + targetFactory, 'server', 'provisioning_api', (preset) { - late DockerContainer container; + late TestTargetInstance target; late NextcloudClient client; setUpAll(() async { - container = await DockerContainer.create(preset); - client = await TestNextcloudClient.create( - container, + target = await targetFactory.spawn(preset); + client = await target.createClient( username: 'admin', ); }); tearDownAll(() async { - await container.destroy(); + await target.destroy(); }); group('Users', () { diff --git a/packages/nextcloud/test/settings_test.dart b/packages/nextcloud/test/settings_test.dart index 0967cfb88bd..e119974f647 100644 --- a/packages/nextcloud/test/settings_test.dart +++ b/packages/nextcloud/test/settings_test.dart @@ -6,21 +6,23 @@ import 'package:nextcloud_test/nextcloud_test.dart'; import 'package:test/test.dart'; void main() { + final targetFactory = TestTargetFactory.create(); + presets( + targetFactory, 'server', 'settings', (preset) { - late DockerContainer container; + late TestTargetInstance target; late NextcloudClient client; setUpAll(() async { - container = await DockerContainer.create(preset); - client = await TestNextcloudClient.create( - container, + target = await targetFactory.spawn(preset); + client = await target.createClient( username: 'admin', ); }); tearDownAll(() async { - await container.destroy(); + await target.destroy(); }); group('Logs', () { diff --git a/packages/nextcloud/test/spreed_test.dart b/packages/nextcloud/test/spreed_test.dart index 3fa8481b334..3c9e98c51b8 100644 --- a/packages/nextcloud/test/spreed_test.dart +++ b/packages/nextcloud/test/spreed_test.dart @@ -11,18 +11,21 @@ import 'package:test/test.dart'; import 'package:version/version.dart'; void main() { + final targetFactory = TestTargetFactory.create(); + presets( + targetFactory, 'spreed', 'spreed', (preset) { - late DockerContainer container; + late TestTargetInstance target; late NextcloudClient client1; setUpAll(() async { - container = await DockerContainer.create(preset); - client1 = await TestNextcloudClient.create(container); + target = await targetFactory.spawn(preset); + client1 = await target.createClient(); }); tearDownAll(() async { - await container.destroy(); + await target.destroy(); }); Future createTestRoom() async => (await client1.spreed.room.createRoom( @@ -551,8 +554,7 @@ void main() { final room1 = (await client1.spreed.room.joinRoom(token: room.token)).body.ocs.data; await client1.spreed.call.joinCall(token: room.token); - final client2 = await TestNextcloudClient.create( - container, + final client2 = await target.createClient( username: 'user2', ); diff --git a/packages/nextcloud/test/tables_test.dart b/packages/nextcloud/test/tables_test.dart index ea6d69fd8d5..755fb5f449c 100644 --- a/packages/nextcloud/test/tables_test.dart +++ b/packages/nextcloud/test/tables_test.dart @@ -7,18 +7,21 @@ import 'package:timezone/timezone.dart' as tz; import 'package:version/version.dart'; void main() { + final targetFactory = TestTargetFactory.create(); + presets( + targetFactory, 'tables', 'tables', (preset) { - late DockerContainer container; + late TestTargetInstance target; late NextcloudClient client; setUpAll(() async { - container = await DockerContainer.create(preset); - client = await TestNextcloudClient.create(container); + target = await targetFactory.spawn(preset); + client = await target.createClient(); }); tearDownAll(() async { - await container.destroy(); + await target.destroy(); }); test('Is supported', () async { diff --git a/packages/nextcloud/test/uppush_test.dart b/packages/nextcloud/test/uppush_test.dart index 27cfdef462a..472aa90ea10 100644 --- a/packages/nextcloud/test/uppush_test.dart +++ b/packages/nextcloud/test/uppush_test.dart @@ -4,21 +4,23 @@ import 'package:nextcloud_test/nextcloud_test.dart'; import 'package:test/test.dart'; void main() { + final targetFactory = TestTargetFactory.create(); + presets( + targetFactory, 'uppush', 'uppush', (preset) { - late DockerContainer container; + late TestTargetInstance target; late NextcloudClient client; setUpAll(() async { - container = await DockerContainer.create(preset); - client = await TestNextcloudClient.create( - container, + target = await targetFactory.spawn(preset); + client = await target.createClient( username: 'admin', ); }); tearDownAll(() async { - await container.destroy(); + await target.destroy(); }); test('Is installed', () async { diff --git a/packages/nextcloud/test/user_status_test.dart b/packages/nextcloud/test/user_status_test.dart index f690cae9cd8..4a14ac35865 100644 --- a/packages/nextcloud/test/user_status_test.dart +++ b/packages/nextcloud/test/user_status_test.dart @@ -6,18 +6,21 @@ import 'package:nextcloud_test/nextcloud_test.dart'; import 'package:test/test.dart'; void main() { + final targetFactory = TestTargetFactory.create(); + presets( + targetFactory, 'server', 'user_status', (preset) { - late DockerContainer container; + late TestTargetInstance target; late NextcloudClient client; setUpAll(() async { - container = await DockerContainer.create(preset); - client = await TestNextcloudClient.create(container); + target = await targetFactory.spawn(preset); + client = await target.createClient(); }); tearDownAll(() async { - await container.destroy(); + await target.destroy(); }); setUp(() async { diff --git a/packages/nextcloud/test/version_check_test.dart b/packages/nextcloud/test/version_check_test.dart index 69d8205ca2b..fb05f0c6a15 100644 --- a/packages/nextcloud/test/version_check_test.dart +++ b/packages/nextcloud/test/version_check_test.dart @@ -9,14 +9,12 @@ void main() { final check1 = VersionCheck( versions: BuiltList([Version(1, 0, 0)]), minimumVersion: Version(1, 0, 0), - maximumMajor: 1, isSupportedOverride: false, ); final check2 = VersionCheck( versions: BuiltList([Version(1, 0, 0)]), minimumVersion: Version(1, 0, 0), - maximumMajor: 1, isSupportedOverride: false, ); @@ -28,7 +26,6 @@ void main() { versions: null, // Invalid constraints to avoid accidental validation minimumVersion: Version(2, 0, 0), - maximumMajor: 1, ); expect(check.isSupported, isTrue); }); @@ -38,7 +35,6 @@ void main() { versions: BuiltList(), // Invalid constraints to avoid accidental validation minimumVersion: Version(2, 0, 0), - maximumMajor: 1, ); expect(check.isSupported, isTrue); }); @@ -51,69 +47,8 @@ void main() { Version(2, 0, 0), ]), minimumVersion: Version(1, 0, 0), - maximumMajor: 1, ); expect(check.isSupported, isTrue); }); - - test('With maximumMajor', () { - var check = VersionCheck( - versions: BuiltList([Version(0, 9, 9)]), - minimumVersion: Version(1, 0, 0), - maximumMajor: 1, - ); - expect(check.isSupported, isFalse); - - check = VersionCheck( - versions: BuiltList([Version(1, 0, 0)]), - minimumVersion: Version(1, 0, 0), - maximumMajor: 1, - ); - expect(check.isSupported, isTrue); - - check = VersionCheck( - versions: BuiltList([Version(1, 5, 0)]), - minimumVersion: Version(1, 0, 0), - maximumMajor: 1, - ); - expect(check.isSupported, isTrue); - - check = VersionCheck( - versions: BuiltList([Version(1, 9, 9)]), - minimumVersion: Version(1, 0, 0), - maximumMajor: 1, - ); - expect(check.isSupported, isTrue); - - check = VersionCheck( - versions: BuiltList([Version(2, 0, 0)]), - minimumVersion: Version(1, 0, 0), - maximumMajor: 1, - ); - expect(check.isSupported, isFalse); - }); - - test('Without maximumMajor', () { - var check = VersionCheck( - versions: BuiltList([Version(0, 9, 9)]), - minimumVersion: Version(1, 0, 0), - maximumMajor: null, - ); - expect(check.isSupported, isFalse); - - check = VersionCheck( - versions: BuiltList([Version(1, 5, 0)]), - minimumVersion: Version(1, 0, 0), - maximumMajor: null, - ); - expect(check.isSupported, isTrue); - - check = VersionCheck( - versions: BuiltList([Version(2, 0, 0)]), - minimumVersion: Version(1, 0, 0), - maximumMajor: null, - ); - expect(check.isSupported, isFalse); - }); }); } diff --git a/packages/nextcloud/test/weather_status_test.dart b/packages/nextcloud/test/weather_status_test.dart index 381a81015e8..6eddbf24522 100644 --- a/packages/nextcloud/test/weather_status_test.dart +++ b/packages/nextcloud/test/weather_status_test.dart @@ -7,18 +7,21 @@ import 'package:test/test.dart'; import 'package:version/version.dart'; void main() { + final targetFactory = TestTargetFactory.create(); + presets( + targetFactory, 'server', 'weather_status', (preset) { - late DockerContainer container; + late TestTargetInstance target; late NextcloudClient client; setUpAll(() async { - container = await DockerContainer.create(preset); - client = await TestNextcloudClient.create(container); + target = await targetFactory.spawn(preset); + client = await target.createClient(); }); tearDownAll(() async { - await container.destroy(); + await target.destroy(); }); test('Set mode', () async { diff --git a/packages/nextcloud/test/webdav_test.dart b/packages/nextcloud/test/webdav_test.dart index 86df0eb5d52..d38ca045e2f 100644 --- a/packages/nextcloud/test/webdav_test.dart +++ b/packages/nextcloud/test/webdav_test.dart @@ -191,16 +191,19 @@ void main() { }); }); + final targetFactory = TestTargetFactory.create(); + presets( + targetFactory, 'server', 'webdav', (preset) { - late DockerContainer container; + late TestTargetInstance target; late NextcloudClient client; setUpAll(() async { - container = await DockerContainer.create(preset); - client = await TestNextcloudClient.create(container); + target = await targetFactory.spawn(preset); + client = await target.createClient(); await client.webdav.mkcol(PathUri.parse('test')); resetFixture(); @@ -209,7 +212,7 @@ void main() { closeFixture(); await client.webdav.delete(PathUri.parse('test')); - await container.destroy(); + await target.destroy(); }); test('List directory', () async { diff --git a/packages/nextcloud_test/bin/generate_presets.dart b/packages/nextcloud_test/bin/generate_presets.dart index 00d45ac9e93..e0cfdcd311c 100644 --- a/packages/nextcloud_test/bin/generate_presets.dart +++ b/packages/nextcloud_test/bin/generate_presets.dart @@ -32,6 +32,9 @@ Future main() async { for (final release in app.releases) { final serverVersion = release.findLatestServerVersion(serverVersions); + if (serverVersion == null) { + continue; + } final buffer = StringBuffer()..writeln('SERVER_VERSION=$serverVersion'); @@ -108,7 +111,7 @@ Future> _getServerVersions(http.Client httpClient) async { final version = Version.parse(tag['name'] as String); final normalizedVersion = Version(version.major, version.minor, 0); - if (version < core.minVersion || version.major > core.maxMajor) { + if (version < core.minVersion) { continue; } @@ -163,9 +166,6 @@ Future> _getApps(List appIDs, http.Client httpClient) async { if (maximumServerVersionRequirement < core.minVersion) { continue; } - if (minimumServerVersionRequirement.major > core.maxMajor) { - continue; - } final download = release['download'] as String; diff --git a/packages/nextcloud_test/docker/Dockerfile b/packages/nextcloud_test/docker/Dockerfile index dce4bec375d..3281c481d58 100644 --- a/packages/nextcloud_test/docker/Dockerfile +++ b/packages/nextcloud_test/docker/Dockerfile @@ -3,21 +3,11 @@ FROM nextcloud:${SERVER_VERSION}-fpm-alpine AS nextcloud WORKDIR /usr/src/nextcloud -RUN ./occ maintenance:install --admin-pass admin --admin-email admin@example.com -RUN ./occ config:system:set allow_local_remote_servers --value=true -RUN ./occ config:system:set trusted_domains 1 --value="*" -RUN ./occ config:system:set debug --value=true --type=boolean -RUN ./occ config:system:set loglevel --value=0 --type=integer - -RUN ./occ app:disable password_policy -RUN OC_PASS="user1" ./occ user:add --password-from-env --display-name "User One" user1 -RUN OC_PASS="user2" ./occ user:add --password-from-env --display-name "User Two" user2 -RUN OC_PASS="demo" ./occ user:add --password-from-env --display-name "Demo" demo -RUN ./occ app:enable password_policy +ADD pre.sh /tmp +RUN /tmp/pre.sh RUN (sh /entrypoint.sh php -S 0.0.0.0:80 &) && \ until curl -s -o /dev/null http://localhost/status.php; do true; done && \ - # Do not setup the demo user here for user in admin user1 user2; do curl -u "$user:$user" -H "ocs-apirequest: true" -s -o /dev/null http://localhost/ocs/v2.php/cloud/user; done COPY static /usr/src/nextcloud/static @@ -65,23 +55,12 @@ RUN curl -L "$DROP_ACCOUNT_URL" | tar -xz -C / FROM nextcloud COPY --from=cookbook /cookbook /usr/src/nextcloud/apps/cookbook -RUN ./occ app:enable cookbook - COPY --from=news /news /usr/src/nextcloud/apps/news -RUN ./occ app:enable news - COPY --from=notes /notes /usr/src/nextcloud/apps/notes -RUN ./occ app:enable notes - COPY --from=uppush /uppush /usr/src/nextcloud/apps/uppush -RUN ./occ app:enable uppush - COPY --from=spreed /spreed /usr/src/nextcloud/apps/spreed -RUN ./occ app:enable spreed -RUN ./occ talk:turn:add turn,turns staticauth.openrelay.metered.ca:443 udp,tcp --secret openrelayprojectsecret - COPY --from=tables /tables /usr/src/nextcloud/apps/tables -RUN ./occ app:enable tables - COPY --from=drop_account /drop_account /usr/src/nextcloud/apps/drop_account -RUN ./occ app:enable drop_account + +ADD post.sh /tmp +RUN /tmp/post.sh diff --git a/packages/nextcloud_test/docker/local.sh b/packages/nextcloud_test/docker/local.sh new file mode 100755 index 00000000000..29c7318e8bc --- /dev/null +++ b/packages/nextcloud_test/docker/local.sh @@ -0,0 +1,27 @@ +#!/bin/sh +set -eux + +rm -rf data config/config.php static + +"$(dirname "$0")/pre.sh" + +PHP_CLI_SERVER_WORKERS=10 php -S 0.0.0.0:8080 & +pid="$!" +cleanup() { + kill "$pid" +} +trap cleanup EXIT + +until curl -s -o /dev/null http://localhost:8080/status.php; do true; done + +cp -r "$(dirname "$0")/static" . +for user in admin user1 user2; do + curl -u "$user:$user" -H "ocs-apirequest: true" -s -o /dev/null http://localhost:8080/ocs/v2.php/cloud/user + cp -r "$(dirname "$0")/assets/Recipes" "data/$user/files" +done +./occ files:scan --all + +"$(dirname "$0")/post.sh" --force + +echo "Ready!" +sleep infinity diff --git a/packages/nextcloud_test/docker/post.sh b/packages/nextcloud_test/docker/post.sh new file mode 100755 index 00000000000..84dacd71d2b --- /dev/null +++ b/packages/nextcloud_test/docker/post.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -eux + +./occ app:enable cookbook "$@" +./occ app:enable news "$@" +./occ app:enable notes "$@" +./occ app:enable uppush "$@" +./occ app:enable spreed "$@" +./occ talk:turn:add turn,turns staticauth.openrelay.metered.ca:443 udp,tcp --secret openrelayprojectsecret +./occ app:enable tables "$@" +./occ app:enable drop_account "$@" diff --git a/packages/nextcloud_test/docker/pre.sh b/packages/nextcloud_test/docker/pre.sh new file mode 100755 index 00000000000..6d07b1a7fa2 --- /dev/null +++ b/packages/nextcloud_test/docker/pre.sh @@ -0,0 +1,13 @@ +#!/bin/sh +set -eux + +./occ maintenance:install --admin-pass admin --admin-email admin@example.com +./occ config:system:set allow_local_remote_servers --value=true +./occ config:system:set trusted_domains 1 --value="*" +./occ config:system:set debug --value=true --type=boolean +./occ config:system:set loglevel --value=0 --type=integer + +./occ app:disable password_policy +OC_PASS="user1" ./occ user:add --password-from-env --display-name "User One" user1 +OC_PASS="user2" ./occ user:add --password-from-env --display-name "User Two" user2 +./occ app:enable password_policy diff --git a/packages/nextcloud_test/lib/nextcloud_test.dart b/packages/nextcloud_test/lib/nextcloud_test.dart index 2d0eff6c47a..bb8856958f8 100644 --- a/packages/nextcloud_test/lib/nextcloud_test.dart +++ b/packages/nextcloud_test/lib/nextcloud_test.dart @@ -1,5 +1,4 @@ export 'src/defaults.dart'; -export 'src/docker_container.dart'; export 'src/fixtures.dart' hide appendFixture; export 'src/presets.dart'; -export 'src/test_client.dart'; +export 'src/test_target/test_target.dart'; diff --git a/packages/nextcloud_test/lib/src/app.dart b/packages/nextcloud_test/lib/src/app.dart index d16603ee178..3d33ea2ad7b 100644 --- a/packages/nextcloud_test/lib/src/app.dart +++ b/packages/nextcloud_test/lib/src/app.dart @@ -40,7 +40,7 @@ extension AppFindLatestRelease on App { @internal extension AppReleaseFindLatestServerVersion on AppRelease { - Version findLatestServerVersion(List serverVersions) { + Version? findLatestServerVersion(List serverVersions) { final compatibleReleases = serverVersions .where( (serverVersion) => @@ -48,6 +48,6 @@ extension AppReleaseFindLatestServerVersion on AppRelease { ) .toList() ..sort((a, b) => b.compareTo(a)); - return compatibleReleases.first; + return compatibleReleases.firstOrNull; } } diff --git a/packages/nextcloud_test/lib/src/docker_container.dart b/packages/nextcloud_test/lib/src/docker_container.dart deleted file mode 100644 index d4a2b4c6666..00000000000 --- a/packages/nextcloud_test/lib/src/docker_container.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'dart:async'; -import 'dart:math'; - -import 'package:nextcloud_test/src/presets.dart'; -import 'package:process_run/process_run.dart'; - -int _randomPort() => 1024 + Random().nextInt(65535 - 1024); - -/// Represents a docker container on the system. -class DockerContainer { - DockerContainer._({ - required this.id, - required this.port, - }); - - /// Creates a new docker container and returns its representation. - static Future create(Preset preset) async { - final dockerImageName = 'ghcr.io/nextcloud/neon/dev:${preset.name}-${preset.version.major}.${preset.version.minor}'; - - var result = await runExecutableArguments( - 'docker', - [ - 'images', - '-q', - dockerImageName, - ], - ); - if (result.exitCode != 0) { - throw Exception('Querying docker image failed: ${result.stderr}'); - } - if (result.stdout.toString().isEmpty) { - throw Exception('Missing docker image $dockerImageName. Please build it using ./tool/build-dev-container.sh'); - } - - late int port; - while (true) { - port = _randomPort(); - result = await runExecutableArguments( - 'docker', - [ - 'run', - '--rm', - '-d', - '--add-host', - 'host.docker.internal:host-gateway', - '-p', - '$port:80', - dockerImageName, - ], - ); - // 125 means the docker run command itself has failed which indicated the port is already used - if (result.exitCode != 125) { - break; - } - } - - if (result.exitCode != 0) { - throw Exception('Failed to run docker container: ${result.stderr}'); - } - - return DockerContainer._( - id: result.stdout.toString().replaceAll('\n', ''), - port: port, - ); - } - - /// ID of the docker container. - final String id; - - /// Assigned port of docker container. - final int port; - - /// Removes the docker container from the system. - Future destroy() => runExecutableArguments( - 'docker', - [ - 'kill', - id, - ], - ); -} diff --git a/packages/nextcloud_test/lib/src/fixtures.dart b/packages/nextcloud_test/lib/src/fixtures.dart index 1c2911216c4..1d314c21afd 100644 --- a/packages/nextcloud_test/lib/src/fixtures.dart +++ b/packages/nextcloud_test/lib/src/fixtures.dart @@ -30,7 +30,7 @@ void resetFixture() { /// Validates that the requests match the stored fixtures. /// /// If there is no stored fixture a new one is created. -void validateFixture(Preset preset) { +void validateFixture(Preset? preset) { if (_fixture.isEmpty) { return; } @@ -46,20 +46,17 @@ void validateFixture(Preset preset) { groups.add(group.name.replaceFirst('${groups.join(' ')} ', '')); } - // Remove the groups that are the preset name and the preset version and the app is kept. - for (var i = 0; i <= 2; i++) { - if (groups[i] == '${preset.version.major}.${preset.version.minor}') { - if (i == 1) { - // Remove preset version - groups.removeAt(1); - } else { - groups - // Remove preset version - ..removeAt(2) - // Remove preset group - ..removeAt(0); + if (groups.isNotEmpty && groups[0] == 'server') { + groups.removeAt(0); + } + + if (preset != null) { + // Remove the groups that are the preset name and the preset version and the app is kept. + for (var i = 0; i <= 2; i++) { + if (groups[i] == '${preset.version.major}.${preset.version.minor}') { + groups.removeAt(i); + break; } - break; } } diff --git a/packages/nextcloud_test/lib/src/presets.dart b/packages/nextcloud_test/lib/src/presets.dart index b86496a30ba..1b4e28cf14e 100644 --- a/packages/nextcloud_test/lib/src/presets.dart +++ b/packages/nextcloud_test/lib/src/presets.dart @@ -1,46 +1,26 @@ -import 'package:nextcloud/webdav.dart'; import 'package:nextcloud_test/nextcloud_test.dart'; import 'package:test/test.dart'; -import 'package:universal_io/io.dart'; import 'package:version/version.dart'; /// Combination of preset `name` and preset `version`. typedef Preset = ({String name, Version version}); -final Map> _presets = () { - final presets = >{}; - - final presetGroups = Directory('../nextcloud_test/docker/presets') - .listSync(followLinks: false) - .whereType() - .map((d) => PathUri.parse(d.path).name); - - for (final presetGroup in presetGroups) { - final presetVersions = Directory('../nextcloud_test/docker/presets/$presetGroup') - .listSync(followLinks: false) - .whereType() - .map((f) => Version.parse(PathUri.parse(f.path).name)); - - presets[presetGroup] = presetVersions.toList(); - } - - return presets; -}(); - /// All tests for apps that depend on the server version must be wrapped with this method and pass along the preset. void presets( + TestTargetFactory targetFactory, String presetGroup, String app, dynamic Function(Preset preset) body, { int? retry, Timeout? timeout, }) { - if (!_presets.containsKey(presetGroup)) { + final presets = targetFactory.getPresets(); + if (!presets.containsKey(presetGroup)) { throw Exception('Unknown preset type "$presetGroup"'); } void innerBody() { - for (final presetVersion in _presets[presetGroup]!) { + for (final presetVersion in presets[presetGroup]!) { group('${presetVersion.major}.${presetVersion.minor}', () { final preset = (name: presetGroup, version: presetVersion); @@ -48,7 +28,7 @@ void presets( validateFixture(preset); }); - return body(preset); + body(preset); }); } } diff --git a/packages/nextcloud_test/lib/src/test_client.dart b/packages/nextcloud_test/lib/src/test_client.dart deleted file mode 100644 index 2b845d21193..00000000000 --- a/packages/nextcloud_test/lib/src/test_client.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:cookie_store/cookie_store.dart'; -import 'package:neon_http_client/neon_http_client.dart'; -import 'package:nextcloud/nextcloud.dart'; -import 'package:nextcloud_test/src/docker_container.dart'; -import 'package:nextcloud_test/src/fixtures.dart'; -import 'package:nextcloud_test/src/proxy_http_client.dart'; -import 'package:process_run/process_run.dart'; - -/// An extension for creating [NextcloudClient]s based on [DockerContainer]s. -extension TestNextcloudClient on NextcloudClient { - /// Creates a new [NextcloudClient] for a given [container] and [username]. - /// - /// It is expected that the password of the user matches the its [username]. - /// This is the case for the available test docker containers. - static Future create( - DockerContainer container, { - String? username = 'user1', - }) async { - String? appPassword; - if (username != null) { - final inputStream = StreamController>(); - final process = runExecutableArguments( - 'docker', - [ - 'exec', - '-i', - container.id, - 'php', - '-f', - 'occ', - 'user:add-app-password', - username, - ], - stdin: inputStream.stream, - ); - inputStream.add(utf8.encode(username)); - await inputStream.close(); - - final result = await process; - if (result.exitCode != 0) { - throw Exception('Failed to run generate app password command\n${result.stderr}\n${result.stdout}'); - } - appPassword = (result.stdout as String).split('\n')[1]; - } - - final url = Uri( - scheme: 'http', - host: 'localhost', - port: container.port, - ); - - final httpClient = NeonHttpClient( - baseURL: url, - cookieStore: CookieStore(), - client: getProxyHttpClient( - onRequest: appendFixture, - ), - ); - - return NextcloudClient( - url, - loginName: username, - password: username, - appPassword: appPassword, - httpClient: httpClient, - ); - } -} diff --git a/packages/nextcloud_test/lib/src/test_target/docker_container.dart b/packages/nextcloud_test/lib/src/test_target/docker_container.dart new file mode 100644 index 00000000000..14221f916b0 --- /dev/null +++ b/packages/nextcloud_test/lib/src/test_target/docker_container.dart @@ -0,0 +1,155 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; + +import 'package:nextcloud/webdav.dart'; +import 'package:nextcloud_test/src/presets.dart'; +import 'package:nextcloud_test/src/test_target/test_target.dart'; +import 'package:process_run/process_run.dart'; +import 'package:version/version.dart'; + +int _randomPort() => 1024 + Random().nextInt(65535 - 1024); + +/// Factory for spawning docker containers as test targets. +class DockerContainerFactory implements TestTargetFactory { + /// Creates a new docker container and returns its representation. + @override + Future spawn(Preset? preset) async { + final dockerImageName = + 'ghcr.io/nextcloud/neon/dev:${preset!.name}-${preset.version.major}.${preset.version.minor}'; + + var result = await runExecutableArguments( + 'docker', + [ + 'images', + '-q', + dockerImageName, + ], + ); + if (result.exitCode != 0) { + throw Exception('Querying docker image failed: ${result.stderr}'); + } + if (result.stdout.toString().isEmpty) { + throw Exception('Missing docker image $dockerImageName. Please build it using ./tool/build-dev-container.sh'); + } + + late int port; + while (true) { + port = _randomPort(); + result = await runExecutableArguments( + 'docker', + [ + 'run', + '--rm', + '-d', + '--add-host', + 'host.docker.internal:host-gateway', + '-p', + '$port:80', + dockerImageName, + ], + ); + // 125 means the docker run command itself has failed which indicated the port is already used + if (result.exitCode != 125) { + break; + } + } + + if (result.exitCode != 0) { + throw Exception('Failed to run docker container: ${result.stderr}'); + } + + return DockerContainerInstance( + id: result.stdout.toString().replaceAll('\n', ''), + port: port, + ); + } + + @override + Map> getPresets() { + final presets = >{}; + + final presetGroups = Directory('../nextcloud_test/docker/presets') + .listSync(followLinks: false) + .whereType() + .map((d) => PathUri.parse(d.path).name); + + for (final presetGroup in presetGroups) { + final presetVersions = Directory('../nextcloud_test/docker/presets/$presetGroup') + .listSync(followLinks: false) + .whereType() + .map((f) => Version.parse(PathUri.parse(f.path).name)); + + presets[presetGroup] = presetVersions.toList(); + } + + return presets; + } +} + +/// Test target representing a docker container. +class DockerContainerInstance extends TestTargetInstance { + /// Creates a new Docker container instance. + DockerContainerInstance({ + required this.id, + required this.port, + }); + + /// ID of the docker container. + final String id; + + /// Assigned port of docker container. + final int port; + + /// Removes the docker container from the system. + @override + Future destroy() => runExecutableArguments( + 'docker', + [ + 'kill', + id, + ], + ); + + @override + late Uri hostURL = Uri( + scheme: 'http', + host: 'localhost', + port: port, + ); + + @override + Uri targetURL = Uri( + scheme: 'http', + host: 'localhost', + ); + + @override + Future createAppPassword(String username) async { + final inputStream = StreamController>(); + final process = runExecutableArguments( + 'docker', + [ + 'exec', + '-i', + id, + 'php', + '-f', + 'occ', + 'user:add-app-password', + username, + ], + stdin: inputStream.stream, + ); + inputStream.add(utf8.encode(username)); + await inputStream.close(); + + final result = await process; + if (result.exitCode != 0) { + throw Exception('Failed to run generate app password command\n${result.stderr}\n${result.stdout}'); + } + + return (result.stdout as String).split('\n')[1]; + } +} diff --git a/packages/nextcloud_test/lib/src/test_target/local.dart b/packages/nextcloud_test/lib/src/test_target/local.dart new file mode 100644 index 00000000000..be54b39f65b --- /dev/null +++ b/packages/nextcloud_test/lib/src/test_target/local.dart @@ -0,0 +1,114 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:nextcloud_test/src/presets.dart'; +import 'package:nextcloud_test/src/test_target/test_target.dart'; +import 'package:process_run/process_run.dart'; +import 'package:version/version.dart'; + +/// Factory for running tests against a local instance. +class LocalFactory implements TestTargetFactory { + @override + LocalInstance spawn(Preset? preset) => LocalInstance(); + + @override + Map> getPresets() { + final presets = >{}; + final regex = RegExp(' - (.*): (.*)'); + + final dir = Platform.environment['DIR']!; + + var result = runExecutableArgumentsSync( + 'php', + [ + '-f', + './occ', + 'app:list', + '--enabled', + ], + workingDirectory: dir, + ); + if (result.exitCode != 0) { + throw Exception('Failed to list apps\n${result.stderr}\n${result.stdout}'); + } + + for (final line in (result.stdout as String).split('\n')) { + final matches = regex.allMatches(line); + + if (matches.isNotEmpty) { + final match = matches.single; + + presets[match.group(1)!] = [Version.parse(match.group(2)!)]; + } + } + + result = runExecutableArgumentsSync( + 'php', + [ + '-f', + './occ', + 'status', + ], + workingDirectory: dir, + ); + if (result.exitCode != 0) { + throw Exception('Failed to get status\n${result.stderr}\n${result.stdout}'); + } + + for (final line in (result.stdout as String).split('\n')) { + final matches = regex.allMatches(line); + + if (matches.isNotEmpty) { + final match = matches.single; + if (match.group(1)! != 'version') { + continue; + } + + presets['server'] = [Version.parse(match.group(2)!)]; + break; + } + } + + return presets; + } +} + +/// Test target representing a local instance. +class LocalInstance extends TestTargetInstance { + @override + void destroy() {} + + @override + Uri hostURL = Uri.parse(Platform.environment['URL']!); + + @override + late Uri targetURL = hostURL; + + @override + Future createAppPassword(String username) async { + final dir = Platform.environment['DIR']!; + + final inputStream = StreamController>(); + final process = runExecutableArguments( + 'php', + [ + '-f', + 'occ', + 'user:add-app-password', + username, + ], + stdin: inputStream.stream, + workingDirectory: dir, + ); + inputStream.add(utf8.encode(username)); + await inputStream.close(); + + final result = await process; + if (result.exitCode != 0) { + throw Exception('Failed to run generate app password command\n${result.stderr}\n${result.stdout}'); + } + + return (result.stdout as String).split('\n')[1]; + } +} diff --git a/packages/nextcloud_test/lib/src/test_target/test_target.dart b/packages/nextcloud_test/lib/src/test_target/test_target.dart new file mode 100644 index 00000000000..db7bcd32903 --- /dev/null +++ b/packages/nextcloud_test/lib/src/test_target/test_target.dart @@ -0,0 +1,75 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:cookie_store/cookie_store.dart'; +import 'package:neon_http_client/neon_http_client.dart'; +import 'package:nextcloud/nextcloud.dart'; +import 'package:nextcloud_test/nextcloud_test.dart'; +import 'package:nextcloud_test/src/fixtures.dart'; +import 'package:nextcloud_test/src/proxy_http_client.dart'; +import 'package:nextcloud_test/src/test_target/docker_container.dart'; +import 'package:nextcloud_test/src/test_target/local.dart'; +import 'package:version/version.dart'; + +/// Factory for creating [TestTargetInstance]s. +abstract interface class TestTargetFactory { + /// Creates a new [TestTargetFactory]. + static TestTargetFactory create() { + final url = Platform.environment['URL']; + final dir = Platform.environment['DIR']; + if (url != null && url.isNotEmpty && dir != null && dir.isNotEmpty) { + return LocalFactory(); + } + + return DockerContainerFactory(); + } + + /// Spawns a new [T]. + FutureOr spawn(Preset? preset); + + /// Returns the available presets for the factory. + Map> getPresets(); +} + +/// Instance of a test target. +abstract class TestTargetInstance { + /// Destroys the instance. + FutureOr destroy(); + + /// URL where the target is available from the host side. + Uri get hostURL; + + /// URL where the target is available from itself. + Uri get targetURL; + + /// Creates an app password for [username] on the instance. + Future createAppPassword(String username); + + /// Creates a new [NextcloudClient] for a given [username]. + /// + /// It is expected that the password of the user matches the its [username]. + Future createClient({ + String? username = 'user1', + }) async { + String? appPassword; + if (username != null) { + appPassword = await createAppPassword(username); + } + + final httpClient = NeonHttpClient( + baseURL: hostURL, + cookieStore: CookieStore(), + client: getProxyHttpClient( + onRequest: appendFixture, + ), + ); + + return NextcloudClient( + hostURL, + loginName: username, + password: username, + appPassword: appPassword, + httpClient: httpClient, + ); + } +}