Compare commits
	
		
			5 Commits 
		
	
	
		
			main
			...
			feat/nextc
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								 | 
						2e6e7b6975 | 2 years ago | 
| 
							
							
								 | 
						93b43b3e82 | 2 years ago | 
| 
							
							
								 | 
						b131f5d172 | 2 years ago | 
| 
							
							
								 | 
						272f922a5a | 2 years ago | 
| 
							
							
								 | 
						9c0d4b0a5d | 2 years ago | 
				 29 changed files with 325 additions and 320 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