A framework for building convergent cross-platform Nextcloud clients using Flutter.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

251 lines
5.8 KiB

2 years ago
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:nextcloud/nextcloud.dart';
import 'package:process_run/cmd_run.dart';
import 'package:test/test.dart';
const retryCount = 3;
const timeout = Timeout(Duration(seconds: 30));
class DockerContainer {
DockerContainer({
required this.id,
required this.port,
});
final String id;
final int port;
2 years ago
Future<void> runOccCommand(final List<String> args) async {
2 years ago
final result = await runExecutableArguments(
'docker',
[
'exec',
id,
2 years ago
'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,
],
),
2 years ago
);
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()}';
2 years ago
}
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) {
2 years ago
final inputStream = StreamController<List<int>>();
final process = runExecutableArguments(
'docker',
[
'exec',
'-i',
container.id,
'php',
2 years ago
'-f',
'occ',
'user:add-app-password',
username,
2 years ago
],
stdin: inputStream.stream,
);
inputStream.add(utf8.encode(username));
2 years ago
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}');
2 years ago
}
appPassword = (result.stdout as String).split('\n')[1];
2 years ago
}
final client = TestNextcloudClient(
Uri(
scheme: 'http',
host: 'localhost',
port: container.port,
),
loginName: username,
password: username,
appPassword: appPassword,
appType: appType,
userAgentOverride: userAgentOverride,
cookieJar: CookieJar(),
);
2 years ago
var i = 0;
while (true) {
try {
await client.core.getStatus();
break;
} on CoreApiException catch (error) {
i++;
await Future.delayed(const Duration(milliseconds: 100));
if (i >= 30) {
throw TimeoutException('Failed to get the status of the Server. $error');
}
}
}
2 years ago
return client;
}
2 years ago
Future<DockerContainer> getDockerContainer(final DockerImage image, {final bool useApache = false}) async {
late ProcessResult result;
late int port;
while (true) {
port = randomPort();
result = await runExecutableArguments(
'docker',
[
'run',
'--rm',
'-d',
'--add-host',
'host.docker.internal:host-gateway',
'-p',
'$port:80',
image,
if (!useApache) ...[
'php',
'-S',
'0.0.0.0:80',
],
],
);
// 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}');
}
2 years ago
return DockerContainer(
id: result.stdout.toString().replaceAll('\n', ''),
port: port,
);
}
typedef DockerImage = String;
1 year ago
Future<DockerImage> getDockerImage({
final String? serverVersion,
}) async {
final buildArgs = <String, String>{
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('-')}';
2 years ago
final inputStream = StreamController<List<int>>();
final process = runExecutableArguments(
'docker',
[
'build',
'-t',
dockerImageName,
1 year ago
...buildArgs.entries
.map((final buildArg) => ['--build-arg', '${buildArg.key}=${buildArg.value}'])
.expand((final buildArg) => buildArg),
'-f',
'-',
'../../tool',
],
stdin: inputStream.stream,
);
inputStream.add(utf8.encode(File('../../tool/Dockerfile.dev').readAsStringSync()));
await inputStream.close();
final result = await process;
if (result.exitCode != 0) {
1 year ago
throw Exception('Failed to build docker image:\n${result.stdout as String}\n${result.stderr as String}');
2 years ago
}
return dockerImageName;
2 years ago
}
class TestNextcloudUser {
TestNextcloudUser(
this.username,
this.password, {
2 years ago
this.displayName,
});
final String username;
final String password;
2 years ago
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);
}