Compare commits
3 Commits
feat/nextc
...
main
Author | SHA1 | Date |
---|---|---|
|
275a130b0f | 1 year ago |
|
e22b6f3ca2 | 1 year ago |
|
805db1a0fc | 1 year ago |
30 changed files with 366 additions and 347 deletions
@ -0,0 +1,227 @@ |
|||||||
|
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); |
||||||
|
} |
@ -1,3 +0,0 @@ |
|||||||
# nextcloud_test |
|
||||||
|
|
||||||
A helper package for running tests in the [nextcloud](../nextcloud) package. |
|
@ -1 +0,0 @@ |
|||||||
include: package:neon_lints/dart.yaml |
|
@ -1,3 +0,0 @@ |
|||||||
export 'src/defaults.dart'; |
|
||||||
export 'src/docker_container.dart'; |
|
||||||
export 'src/test_client.dart'; |
|
@ -1,7 +0,0 @@ |
|||||||
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)); |
|
@ -1,112 +0,0 @@ |
|||||||
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()}'; |
|
||||||
} |
|
@ -1,79 +0,0 @@ |
|||||||
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; |
|
||||||
} |
|
||||||
} |
|
@ -1,21 +0,0 @@ |
|||||||
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 |
|
@ -1,8 +0,0 @@ |
|||||||
# 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 |
|
Loading…
Reference in new issue