Compare commits

...

5 Commits

Author SHA1 Message Date
jld3103 2e6e7b6975
refactor(nextcloud): Migrate to nextcloud_test package 1 year ago
jld3103 93b43b3e82
feat(nextcloud_test): Init 1 year ago
jld3103 b131f5d172
chore(nextcloud): Remove unnecessary proxy helper class 1 year ago
jld3103 272f922a5a
chore(nextcloud): Remove custom date range expect function 1 year ago
jld3103 9c0d4b0a5d
chore(nextcloud): Remove unused helper class 1 year ago
  1. 1
      commitlint.yaml
  2. 2
      cspell.json
  3. 4
      packages/nextcloud/pubspec.yaml
  4. 4
      packages/nextcloud/pubspec_overrides.yaml
  5. 9
      packages/nextcloud/test/core_test.dart
  6. 10
      packages/nextcloud/test/dashboard_test.dart
  7. 227
      packages/nextcloud/test/helper.dart
  8. 9
      packages/nextcloud/test/news_test.dart
  9. 9
      packages/nextcloud/test/notes_test.dart
  10. 26
      packages/nextcloud/test/notifications_test.dart
  11. 10
      packages/nextcloud/test/provisioning_api_test.dart
  12. 10
      packages/nextcloud/test/settings_test.dart
  13. 42
      packages/nextcloud/test/spreed_test.dart
  14. 10
      packages/nextcloud/test/uppush_test.dart
  15. 10
      packages/nextcloud/test/user_status_test.dart
  16. 23
      packages/nextcloud/test/webdav_test.dart
  17. 1
      packages/nextcloud_test/LICENSE
  18. 3
      packages/nextcloud_test/README.md
  19. 1
      packages/nextcloud_test/analysis_options.yaml
  20. 2
      packages/nextcloud_test/docker/Dockerfile
  21. 0
      packages/nextcloud_test/docker/static/nasa.xml
  22. 0
      packages/nextcloud_test/docker/static/wikipedia.xml
  23. 3
      packages/nextcloud_test/lib/nextcloud_test.dart
  24. 7
      packages/nextcloud_test/lib/src/defaults.dart
  25. 112
      packages/nextcloud_test/lib/src/docker_container.dart
  26. 79
      packages/nextcloud_test/lib/src/test_client.dart
  27. 21
      packages/nextcloud_test/pubspec.yaml
  28. 8
      packages/nextcloud_test/pubspec_overrides.yaml
  29. 2
      tool/build-dev-container.sh

1
commitlint.yaml

@ -25,4 +25,5 @@ rules:
- neon_notifications
- neon_lints
- nextcloud
- nextcloud_test
- sort_box

2
cspell.json

@ -14,7 +14,7 @@
"packages/dynamite/dynamite_petstore_example/lib",
"packages/file_icons/lib/src/data.dart",
"packages/neon_lints/lib",
"tool/dev/static"
"packages/nextcloud_test/docker/static"
],
"dictionaries": [
"bash",

4
packages/nextcloud/pubspec.yaml

@ -37,6 +37,10 @@ dev_dependencies:
git:
url: https://github.com/nextcloud/neon
path: packages/neon_lints
nextcloud_test:
git:
url: https://github.com/nextcloud/neon
path: packages/nextcloud_test
path: ^1.8.3
process_run: ^0.13.3
test: ^1.24.9

4
packages/nextcloud/pubspec_overrides.yaml

@ -1,4 +1,4 @@
# melos_managed_dependency_overrides: dynamite,dynamite_runtime,neon_lints
# melos_managed_dependency_overrides: dynamite,dynamite_runtime,neon_lints,nextcloud_test
dependency_overrides:
dynamite:
path: ../dynamite/dynamite
@ -6,3 +6,5 @@ dependency_overrides:
path: ../dynamite/dynamite_runtime
neon_lints:
path: ../neon_lints
nextcloud_test:
path: ../nextcloud_test

9
packages/nextcloud/test/core_test.dart

@ -1,18 +1,17 @@
import 'package:nextcloud/core.dart' as core;
import 'package:nextcloud/nextcloud.dart';
import 'package:nextcloud_test/nextcloud_test.dart';
import 'package:test/test.dart';
import 'helper.dart';
void main() {
group(
'core',
() {
late DockerContainer container;
late TestNextcloudClient client;
late NextcloudClient client;
setUp(() async {
container = await getDockerContainer();
client = await getTestClient(container);
container = await DockerContainer.create();
client = await TestNextcloudClient.create(container);
});
tearDown(() => container.destroy());

10
packages/nextcloud/test/dashboard_test.dart

@ -1,17 +1,17 @@
import 'package:nextcloud/dashboard.dart';
import 'package:nextcloud/nextcloud.dart';
import 'package:nextcloud_test/nextcloud_test.dart';
import 'package:test/test.dart';
import 'helper.dart';
void main() {
group(
'dashboard',
() {
late DockerContainer container;
late TestNextcloudClient client;
late NextcloudClient client;
setUp(() async {
container = await getDockerContainer();
client = await getTestClient(container);
container = await DockerContainer.create();
client = await TestNextcloudClient.create(container);
});
tearDown(() => container.destroy());

227
packages/nextcloud/test/helper.dart

@ -1,227 +0,0 @@
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:nextcloud/core.dart' as core;
import 'package:nextcloud/nextcloud.dart';
import 'package:process_run/cmd_run.dart';
import 'package:test/test.dart';
import 'package:universal_io/io.dart';
const retryCount = 3;
const timeout = Timeout(Duration(seconds: 30));
class DockerContainer {
DockerContainer({
required this.id,
required this.port,
});
final String id;
final int port;
Future<void> runOccCommand(final List<String> args) async {
final result = await runExecutableArguments(
'docker',
[
'exec',
id,
'php',
'-f',
'occ',
...args,
],
stdout: stdout,
stderr: stderr,
);
if (result.exitCode != 0) {
throw Exception('Failed to run occ command');
}
}
void destroy() => unawaited(
runExecutableArguments(
'docker',
[
'kill',
id,
],
),
);
Future<String> serverLogs() async => (await runExecutableArguments(
'docker',
[
'logs',
id,
],
stdoutEncoding: utf8,
))
.stdout as String;
Future<String> nextcloudLogs() async => (await runExecutableArguments(
'docker',
[
'exec',
id,
'cat',
'data/nextcloud.log',
],
stdoutEncoding: utf8,
))
.stdout as String;
Future<String> allLogs() async => '${await serverLogs()}\n\n${await nextcloudLogs()}';
}
class TestNextcloudClient extends NextcloudClient {
TestNextcloudClient(
super.baseURL, {
super.loginName,
super.password,
super.appPassword,
super.language,
super.appType,
super.userAgentOverride,
super.cookieJar,
});
}
Future<TestNextcloudClient> getTestClient(
final DockerContainer container, {
final String? username = 'user1',
final AppType appType = AppType.unknown,
final String? userAgentOverride,
}) async {
String? appPassword;
if (username != null) {
final inputStream = StreamController<List<int>>();
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 client = TestNextcloudClient(
Uri(
scheme: 'http',
host: 'localhost',
port: container.port,
),
loginName: username,
password: username,
appPassword: appPassword,
appType: appType,
userAgentOverride: userAgentOverride,
cookieJar: CookieJar(),
);
var i = 0;
while (true) {
try {
await client.core.getStatus();
break;
} catch (error) {
if (error is HttpException || error is DynamiteApiException) {
i++;
await Future<void>.delayed(const Duration(milliseconds: 100));
if (i >= 300) {
throw TimeoutException('Failed to get the status of the Server. $error');
}
} else {
rethrow;
}
}
}
return client;
}
Future<DockerContainer> getDockerContainer() async {
const dockerImageName = 'ghcr.io/nextcloud/neon/dev';
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,
);
}
class TestNextcloudUser {
TestNextcloudUser(
this.username,
this.password, {
this.displayName,
});
final String username;
final String password;
final String? displayName;
}
int randomPort() => 1024 + Random().nextInt(65535 - 1024);
void expectDateInReasonableTimeRange(final DateTime actual, final DateTime expected) {
const duration = Duration(seconds: 10);
expect(actual.isAfter(expected.subtract(duration)), isTrue);
expect(actual.isBefore(expected.add(duration)), isTrue);
}

9
packages/nextcloud/test/news_test.dart

@ -2,19 +2,18 @@ import 'dart:async';
import 'package:nextcloud/news.dart' as news;
import 'package:nextcloud/nextcloud.dart';
import 'package:nextcloud_test/nextcloud_test.dart';
import 'package:test/test.dart';
import 'helper.dart';
void main() {
group(
'news',
() {
late DockerContainer container;
late TestNextcloudClient client;
late NextcloudClient client;
setUp(() async {
container = await getDockerContainer();
client = await getTestClient(container);
container = await DockerContainer.create();
client = await TestNextcloudClient.create(container);
});
tearDown(() => container.destroy());

9
packages/nextcloud/test/notes_test.dart

@ -1,19 +1,18 @@
import 'package:nextcloud/core.dart' as core;
import 'package:nextcloud/nextcloud.dart';
import 'package:nextcloud/notes.dart' as notes;
import 'package:nextcloud_test/nextcloud_test.dart';
import 'package:test/test.dart';
import 'helper.dart';
void main() {
group(
'notes',
() {
late DockerContainer container;
late TestNextcloudClient client;
late NextcloudClient client;
setUp(() async {
container = await getDockerContainer();
client = await getTestClient(container);
container = await DockerContainer.create();
client = await TestNextcloudClient.create(container);
});
tearDown(() => container.destroy());

26
packages/nextcloud/test/notifications_test.dart

@ -1,17 +1,17 @@
import 'dart:async';
import 'package:nextcloud/nextcloud.dart';
import 'package:nextcloud/notifications.dart';
import 'package:nextcloud_test/nextcloud_test.dart';
import 'package:test/test.dart';
import 'helper.dart';
void main() {
group('notifications', () {
late DockerContainer container;
late TestNextcloudClient client;
late NextcloudClient client;
setUp(() async {
container = await getDockerContainer();
client = await getTestClient(
container = await DockerContainer.create();
client = await TestNextcloudClient.create(
container,
username: 'admin',
);
@ -42,7 +42,10 @@ void main() {
expect(response.ocs.data[0].notificationId, 2);
expect(response.ocs.data[0].app, 'admin_notifications');
expect(response.ocs.data[0].user, 'admin');
expectDateInReasonableTimeRange(DateTime.parse(response.ocs.data[0].datetime), startTime);
expect(
DateTime.parse(response.ocs.data[0].datetime).millisecondsSinceEpoch,
closeTo(startTime.millisecondsSinceEpoch, 10E3),
);
expect(response.ocs.data[0].objectType, 'admin_notifications');
expect(response.ocs.data[0].objectId, isNotNull);
expect(response.ocs.data[0].subject, '123');
@ -67,7 +70,10 @@ void main() {
expect(response.body.ocs.data.notificationId, 2);
expect(response.body.ocs.data.app, 'admin_notifications');
expect(response.body.ocs.data.user, 'admin');
expectDateInReasonableTimeRange(DateTime.parse(response.body.ocs.data.datetime), startTime);
expect(
DateTime.parse(response.body.ocs.data.datetime).millisecondsSinceEpoch,
closeTo(startTime.millisecondsSinceEpoch, 10E3),
);
expect(response.body.ocs.data.objectType, 'admin_notifications');
expect(response.body.ocs.data.objectId, isNotNull);
expect(response.body.ocs.data.subject, '123');
@ -101,10 +107,10 @@ void main() {
group('Push', () {
late DockerContainer container;
late TestNextcloudClient client;
late NextcloudClient client;
setUp(() async {
container = await getDockerContainer();
client = await getTestClient(
container = await DockerContainer.create();
client = await TestNextcloudClient.create(
container,
username: 'admin',
);

10
packages/nextcloud/test/provisioning_api_test.dart

@ -1,17 +1,17 @@
import 'package:nextcloud/nextcloud.dart';
import 'package:nextcloud/provisioning_api.dart';
import 'package:nextcloud_test/nextcloud_test.dart';
import 'package:test/test.dart';
import 'helper.dart';
void main() {
group(
'provisioning_api',
() {
late DockerContainer container;
late TestNextcloudClient client;
late NextcloudClient client;
setUp(() async {
container = await getDockerContainer();
client = await getTestClient(
container = await DockerContainer.create();
client = await TestNextcloudClient.create(
container,
username: 'admin',
);

10
packages/nextcloud/test/settings_test.dart

@ -1,19 +1,19 @@
import 'dart:convert';
import 'package:nextcloud/nextcloud.dart';
import 'package:nextcloud/settings.dart';
import 'package:nextcloud_test/nextcloud_test.dart';
import 'package:test/test.dart';
import 'helper.dart';
void main() {
group(
'settings',
() {
late DockerContainer container;
late TestNextcloudClient client;
late NextcloudClient client;
setUp(() async {
container = await getDockerContainer();
client = await getTestClient(
container = await DockerContainer.create();
client = await TestNextcloudClient.create(
container,
username: 'admin',
);

42
packages/nextcloud/test/spreed_test.dart

@ -3,20 +3,20 @@ import 'dart:convert';
import 'package:built_value/json_object.dart';
import 'package:nextcloud/core.dart' as core;
import 'package:nextcloud/nextcloud.dart';
import 'package:nextcloud/spreed.dart' as spreed;
import 'package:nextcloud_test/nextcloud_test.dart';
import 'package:test/test.dart';
import 'helper.dart';
void main() {
group(
'spreed',
() {
late DockerContainer container;
late TestNextcloudClient client1;
late NextcloudClient client1;
setUp(() async {
container = await getDockerContainer();
client1 = await getTestClient(container);
container = await DockerContainer.create();
client1 = await TestNextcloudClient.create(container);
});
tearDown(() => container.destroy());
@ -183,10 +183,7 @@ void main() {
expect(response.body.ocs.data!.actorType, spreed.ActorType.users.name);
expect(response.body.ocs.data!.actorId, 'user1');
expect(response.body.ocs.data!.actorDisplayName, 'User One');
expectDateInReasonableTimeRange(
DateTime.fromMillisecondsSinceEpoch(response.body.ocs.data!.timestamp * 1000),
startTime,
);
expect(response.body.ocs.data!.timestamp * 1000, closeTo(startTime.millisecondsSinceEpoch, 10E3));
expect(response.body.ocs.data!.message, 'bla');
expect(response.body.ocs.data!.messageType, spreed.MessageType.comment.name);
});
@ -227,10 +224,7 @@ void main() {
expect(response.body.ocs.data[0].actorType, spreed.ActorType.users.name);
expect(response.body.ocs.data[0].actorId, 'user1');
expect(response.body.ocs.data[0].actorDisplayName, 'User One');
expectDateInReasonableTimeRange(
DateTime.fromMillisecondsSinceEpoch(response.body.ocs.data[0].timestamp * 1000),
startTime,
);
expect(response.body.ocs.data[0].timestamp * 1000, closeTo(startTime.millisecondsSinceEpoch, 10E3));
expect(response.body.ocs.data[0].message, '123');
expect(response.body.ocs.data[0].messageType, spreed.MessageType.comment.name);
@ -238,10 +232,7 @@ void main() {
expect(response.body.ocs.data[0].parent!.actorType, spreed.ActorType.users.name);
expect(response.body.ocs.data[0].parent!.actorId, 'user1');
expect(response.body.ocs.data[0].parent!.actorDisplayName, 'User One');
expectDateInReasonableTimeRange(
DateTime.fromMillisecondsSinceEpoch(response.body.ocs.data[0].parent!.timestamp * 1000),
startTime,
);
expect(response.body.ocs.data[0].parent!.timestamp * 1000, closeTo(startTime.millisecondsSinceEpoch, 10E3));
expect(response.body.ocs.data[0].parent!.message, 'bla');
expect(response.body.ocs.data[0].parent!.messageType, spreed.MessageType.comment.name);
@ -249,10 +240,7 @@ void main() {
expect(response.body.ocs.data[1].actorType, spreed.ActorType.users.name);
expect(response.body.ocs.data[1].actorId, 'user1');
expect(response.body.ocs.data[1].actorDisplayName, 'User One');
expectDateInReasonableTimeRange(
DateTime.fromMillisecondsSinceEpoch(response.body.ocs.data[1].timestamp * 1000),
startTime,
);
expect(response.body.ocs.data[1].timestamp * 1000, closeTo(startTime.millisecondsSinceEpoch, 10E3));
expect(response.body.ocs.data[1].message, 'bla');
expect(response.body.ocs.data[1].messageType, spreed.MessageType.comment.name);
@ -260,10 +248,7 @@ void main() {
expect(response.body.ocs.data[2].actorType, spreed.ActorType.users.name);
expect(response.body.ocs.data[2].actorId, 'user1');
expect(response.body.ocs.data[2].actorDisplayName, 'User One');
expectDateInReasonableTimeRange(
DateTime.fromMillisecondsSinceEpoch(response.body.ocs.data[2].timestamp * 1000),
startTime,
);
expect(response.body.ocs.data[2].timestamp * 1000, closeTo(startTime.millisecondsSinceEpoch, 10E3));
expect(response.body.ocs.data[2].message, 'You created the conversation');
expect(response.body.ocs.data[2].systemMessage, 'conversation_created');
expect(response.body.ocs.data[2].messageType, spreed.MessageType.system.name);
@ -300,10 +285,7 @@ void main() {
expect(response.body.ocs.data[0].actorType, spreed.ActorType.users.name);
expect(response.body.ocs.data[0].actorId, 'user1');
expect(response.body.ocs.data[0].actorDisplayName, 'User One');
expectDateInReasonableTimeRange(
DateTime.fromMillisecondsSinceEpoch(response.body.ocs.data[0].timestamp * 1000),
startTime,
);
expect(response.body.ocs.data[0].timestamp * 1000, closeTo(startTime.millisecondsSinceEpoch, 10E3));
expect(response.body.ocs.data[0].message, '123');
expect(response.body.ocs.data[0].messageType, spreed.MessageType.comment.name);
});
@ -377,7 +359,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 getTestClient(
final client2 = await TestNextcloudClient.create(
container,
username: 'user2',
);

10
packages/nextcloud/test/uppush_test.dart

@ -1,17 +1,17 @@
import 'package:nextcloud/nextcloud.dart';
import 'package:nextcloud/uppush.dart';
import 'package:nextcloud_test/nextcloud_test.dart';
import 'package:test/test.dart';
import 'helper.dart';
void main() {
group(
'uppush',
() {
late DockerContainer container;
late TestNextcloudClient client;
late NextcloudClient client;
setUp(() async {
container = await getDockerContainer();
client = await getTestClient(
container = await DockerContainer.create();
client = await TestNextcloudClient.create(
container,
username: 'admin',
);

10
packages/nextcloud/test/user_status_test.dart

@ -1,17 +1,17 @@
import 'package:nextcloud/nextcloud.dart';
import 'package:nextcloud/user_status.dart' as user_status;
import 'package:nextcloud_test/nextcloud_test.dart';
import 'package:test/test.dart';
import 'helper.dart';
void main() {
group(
'user_status',
() {
late DockerContainer container;
late TestNextcloudClient client;
late NextcloudClient client;
setUp(() async {
container = await getDockerContainer();
client = await getTestClient(container);
container = await DockerContainer.create();
client = await TestNextcloudClient.create(container);
});
tearDown(() => container.destroy());

23
packages/nextcloud/test/webdav_test.dart

@ -3,11 +3,10 @@ import 'dart:math';
import 'dart:typed_data';
import 'package:nextcloud/nextcloud.dart';
import 'package:nextcloud_test/nextcloud_test.dart';
import 'package:test/test.dart';
import 'package:universal_io/io.dart';
import 'helper.dart';
void main() {
group('constructUri', () {
for (final values in [
@ -139,11 +138,11 @@ void main() {
'webdav',
() {
late DockerContainer container;
late TestNextcloudClient client;
late NextcloudClient client;
setUp(() async {
container = await getDockerContainer();
client = await getTestClient(container);
container = await DockerContainer.create();
client = await TestNextcloudClient.create(container);
});
tearDown(() => container.destroy());
@ -281,12 +280,18 @@ void main() {
expect(response.isCollection, isTrue);
expect(response.mimeType, isNull);
expect(response.size, data.lengthInBytes);
expectDateInReasonableTimeRange(response.lastModified!, DateTime.now());
expect(
response.lastModified!.millisecondsSinceEpoch,
closeTo(DateTime.now().millisecondsSinceEpoch, 10E3),
);
expect(response.name, 'test');
expect(response.isDirectory, isTrue);
expect(response.props.davgetcontenttype, isNull);
expectDateInReasonableTimeRange(webdavDateFormat.parseUtc(response.props.davgetlastmodified!), DateTime.now());
expect(
webdavDateFormat.parseUtc(response.props.davgetlastmodified!).millisecondsSinceEpoch,
closeTo(DateTime.now().millisecondsSinceEpoch, 10E3),
);
expect(response.props.davresourcetype!.collection, isNotNull);
expect(response.props.ocsize, data.lengthInBytes);
});
@ -355,8 +360,8 @@ void main() {
.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);
expect(props.nccreationtime! * 1000, createdDate.millisecondsSinceEpoch);
expect(props.ncuploadtime! * 1000, closeTo(uploadTime.millisecondsSinceEpoch, 10E3));
});
test('Remove properties', () async {

1
packages/nextcloud_test/LICENSE

@ -0,0 +1 @@
../../assets/AGPL-3.0.txt

3
packages/nextcloud_test/README.md

@ -0,0 +1,3 @@
# nextcloud_test
A helper package for running tests in the [nextcloud](../nextcloud) package.

1
packages/nextcloud_test/analysis_options.yaml

@ -0,0 +1 @@
include: package:neon_lints/dart.yaml

2
tool/Dockerfile.dev → packages/nextcloud_test/docker/Dockerfile

@ -18,7 +18,7 @@ RUN (sh /entrypoint.sh php -S 0.0.0.0:80 &) && \
# 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 dev/static /usr/src/nextcloud/static
COPY static /usr/src/nextcloud/static
ENV PHP_CLI_SERVER_WORKERS=10
CMD ["php", "-S", "0.0.0.0:80"]

0
tool/dev/static/nasa.xml → packages/nextcloud_test/docker/static/nasa.xml

0
tool/dev/static/wikipedia.xml → packages/nextcloud_test/docker/static/wikipedia.xml

3
packages/nextcloud_test/lib/nextcloud_test.dart

@ -0,0 +1,3 @@
export 'src/defaults.dart';
export 'src/docker_container.dart';
export 'src/test_client.dart';

7
packages/nextcloud_test/lib/src/defaults.dart

@ -0,0 +1,7 @@
import 'package:test/test.dart';
/// Default retry count for test groups.
const retryCount = 3;
/// Default timeout for test groups.
const timeout = Timeout(Duration(seconds: 30));

112
packages/nextcloud_test/lib/src/docker_container.dart

@ -0,0 +1,112 @@
import 'dart:async';
import 'dart:convert';
import 'dart:math';
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<DockerContainer> create() async {
const dockerImageName = 'ghcr.io/nextcloud/neon/dev';
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.
void destroy() => unawaited(
runExecutableArguments(
'docker',
[
'kill',
id,
],
),
);
/// Reads the server logs.
Future<String> serverLogs() async => (await runExecutableArguments(
'docker',
[
'logs',
id,
],
stdoutEncoding: utf8,
))
.stdout as String;
/// Reads the Nextcloud logs.
Future<String> nextcloudLogs() async => (await runExecutableArguments(
'docker',
[
'exec',
id,
'cat',
'data/nextcloud.log',
],
stdoutEncoding: utf8,
))
.stdout as String;
/// Reads all logs.
///
/// Combines the output of [serverLogs] and [nextcloudLogs].
Future<String> allLogs() async => '${await serverLogs()}\n\n${await nextcloudLogs()}';
}

79
packages/nextcloud_test/lib/src/test_client.dart

@ -0,0 +1,79 @@
import 'dart:async';
import 'dart:convert';
import 'package:nextcloud/core.dart';
import 'package:nextcloud/nextcloud.dart';
import 'package:nextcloud_test/src/docker_container.dart';
import 'package:process_run/process_run.dart';
import 'package:universal_io/io.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<NextcloudClient> create(
final DockerContainer container, {
final String? username = 'user1',
}) async {
String? appPassword;
if (username != null) {
final inputStream = StreamController<List<int>>();
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 client = NextcloudClient(
Uri(
scheme: 'http',
host: 'localhost',
port: container.port,
),
loginName: username,
password: username,
appPassword: appPassword,
cookieJar: CookieJar(),
);
var i = 0;
while (true) {
try {
await client.core.getStatus();
break;
} catch (error) {
if (error is HttpException || error is DynamiteApiException) {
i++;
await Future<void>.delayed(const Duration(milliseconds: 100));
if (i >= 300) {
throw TimeoutException('Failed to get the status of the Server. $error');
}
} else {
rethrow;
}
}
}
return client;
}
}

21
packages/nextcloud_test/pubspec.yaml

@ -0,0 +1,21 @@
name: nextcloud_test
version: 1.0.0
publish_to: none
environment:
sdk: '>=3.1.0 <4.0.0'
dependencies:
nextcloud:
git:
url: https://github.com/nextcloud/neon
path: packages/nextcloud
process_run: ^0.13.0
test: ^1.24.0
universal_io: ^2.0.0
dev_dependencies:
neon_lints:
git:
url: https://github.com/nextcloud/neon
path: packages/neon_lints

8
packages/nextcloud_test/pubspec_overrides.yaml

@ -0,0 +1,8 @@
# melos_managed_dependency_overrides: dynamite_runtime,neon_lints,nextcloud
dependency_overrides:
dynamite_runtime:
path: ../dynamite/dynamite_runtime
neon_lints:
path: ../neon_lints
nextcloud:
path: ../nextcloud

2
tool/build-dev-container.sh

@ -6,4 +6,4 @@ source tool/common.sh
tag="$(image_tag "dev:latest")"
# shellcheck disable=SC2046
docker buildx build --tag "$tag" $(cache_build_args "$tag") -f - ./tool < tool/Dockerfile.dev
docker buildx build --tag "$tag" $(cache_build_args "$tag") -f - ./packages/nextcloud_test/docker < packages/nextcloud_test/docker/Dockerfile

Loading…
Cancel
Save