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.
250 lines
5.8 KiB
250 lines
5.8 KiB
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; |
|
|
|
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; |
|
} on CoreApiException catch (error) { |
|
i++; |
|
await Future.delayed(const Duration(milliseconds: 100)); |
|
if (i >= 30) { |
|
throw TimeoutException('Failed to get the status of the Server. $error'); |
|
} |
|
} |
|
} |
|
|
|
return client; |
|
} |
|
|
|
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}'); |
|
} |
|
|
|
return DockerContainer( |
|
id: result.stdout.toString().replaceAll('\n', ''), |
|
port: port, |
|
); |
|
} |
|
|
|
typedef DockerImage = String; |
|
|
|
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('-')}'; |
|
|
|
final inputStream = StreamController<List<int>>(); |
|
final process = runExecutableArguments( |
|
'docker', |
|
[ |
|
'build', |
|
'-t', |
|
dockerImageName, |
|
...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) { |
|
throw Exception('Failed to build docker image:\n${result.stdout as String}\n${result.stderr as String}'); |
|
} |
|
|
|
return dockerImageName; |
|
} |
|
|
|
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); |
|
}
|
|
|