Compare commits
5 Commits
main
...
feat/nextc
Author | SHA1 | Date |
---|---|---|
jld3103 | 2e6e7b6975 | 1 year ago |
jld3103 | 93b43b3e82 | 1 year ago |
jld3103 | b131f5d172 | 1 year ago |
jld3103 | 272f922a5a | 1 year ago |
jld3103 | 9c0d4b0a5d | 1 year ago |
30 changed files with 347 additions and 366 deletions
@ -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); |
||||
} |
@ -0,0 +1,3 @@
|
||||
# nextcloud_test |
||||
|
||||
A helper package for running tests in the [nextcloud](../nextcloud) package. |
@ -0,0 +1 @@
|
||||
include: package:neon_lints/dart.yaml |
@ -0,0 +1,3 @@
|
||||
export 'src/defaults.dart'; |
||||
export 'src/docker_container.dart'; |
||||
export 'src/test_client.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)); |
@ -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()}'; |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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 |
@ -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 |
Loading…
Reference in new issue