Merge pull request #5004 from nextcloud/backport/5003/stable26
This commit is contained in:
8
.github/workflows/integration.yml
vendored
8
.github/workflows/integration.yml
vendored
@@ -71,7 +71,7 @@ jobs:
|
||||
path: apps/${{ env.APP_NAME }}
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@2.24.0
|
||||
uses: shivammathur/setup-php@2.25.5
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql, apcu
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
cat config/config.php
|
||||
./occ user:list
|
||||
./occ app:enable --force ${{ env.APP_NAME }}
|
||||
./occ config:system:set query_log_file --value '/home/runner/work/${{ env.APP_NAME }}/${{ env.APP_NAME }}/query.log'
|
||||
./occ config:system:set query_log_file --value "$PWD/query.log"
|
||||
php -S localhost:8080 &
|
||||
|
||||
- name: Run behat
|
||||
@@ -123,12 +123,12 @@ jobs:
|
||||
myError += data.toString()
|
||||
}
|
||||
}
|
||||
await exec.exec(`/bin/bash -c "cat /home/runner/work/${{ env.APP_NAME }}/${{ env.APP_NAME }}/query.log | wc -l"`, [], options)
|
||||
await exec.exec(`/bin/bash -c "cat query.log | wc -l"`, [], options)
|
||||
msg = myOutput
|
||||
const queryCount = parseInt(myOutput, 10)
|
||||
|
||||
myOutput = ''
|
||||
await exec.exec('cat', ['/home/runner/work/${{ env.APP_NAME }}/${{ env.APP_NAME }}/apps/${{ env.APP_NAME }}/tests/integration/base-query-count.txt'], options)
|
||||
await exec.exec('cat', ['apps/${{ env.APP_NAME }}/tests/integration/base-query-count.txt'], options)
|
||||
const baseCount = parseInt(myOutput, 10)
|
||||
|
||||
const absoluteIncrease = queryCount - baseCount
|
||||
|
||||
@@ -16,5 +16,9 @@
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands.js'
|
||||
|
||||
Cypress.on('uncaught:exception', (err) => {
|
||||
return !err.message.includes('ResizeObserver loop limit exceeded')
|
||||
})
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
||||
98
docs/export-import.md
Normal file
98
docs/export-import.md
Normal file
@@ -0,0 +1,98 @@
|
||||
## Export
|
||||
|
||||
Deck currently supports exporting all boards a user owns in a single JSON file. The format is based on the database schema that deck uses. It can be used to re-import boards on the same or other instances.
|
||||
|
||||
The export currently has some kown limitations in terms of specific data not included:
|
||||
- Activity information
|
||||
- File attachments to deck cards
|
||||
- Comments
|
||||
-
|
||||
```
|
||||
occ deck:export > my-file.json
|
||||
```
|
||||
|
||||
## Import boards
|
||||
|
||||
Importing can be done using the API or the `occ` `deck:import` command.
|
||||
|
||||
It is possible to import from the following sources:
|
||||
|
||||
### Deck JSON
|
||||
|
||||
A json file that has been obtained from the above described `occ deck:export [userid]` command can be imported.
|
||||
|
||||
```
|
||||
occ deck:import my-file.json
|
||||
```
|
||||
|
||||
In case you are importing from a different instance you may use an additional config file to provide custom user id mapping in case users have different identifiers.
|
||||
|
||||
```
|
||||
{
|
||||
"owner": "admin",
|
||||
"uidRelation": {
|
||||
"johndoe": "test-user-1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Trello JSON
|
||||
|
||||
Limitations:
|
||||
* Comments with more than 1000 characters are placed as attached files to the card.
|
||||
|
||||
Steps:
|
||||
* Create the data file
|
||||
* Access Trello
|
||||
* go to the board you want to export
|
||||
* Follow the steps in [Trello documentation](https://help.trello.com/article/747-exporting-data-from-trello-1) and export as JSON
|
||||
* Create the configuration file
|
||||
* Execute the import informing the import file path, data file and source as `Trello JSON`
|
||||
|
||||
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/main/lib/Service/Importer/fixtures/config-trelloJson-schema.json) for import `Trello JSON`
|
||||
|
||||
Example configuration file:
|
||||
```json
|
||||
{
|
||||
"owner": "admin",
|
||||
"color": "0800fd",
|
||||
"uidRelation": {
|
||||
"johndoe": "johndoe"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Limitations**:
|
||||
|
||||
Importing from a JSON file imports up to 1000 actions. To find out how many actions the board to be imported has, identify how many actions the JSON has.
|
||||
|
||||
#### Trello API
|
||||
|
||||
Import using API is recommended for boards with more than 1000 actions.
|
||||
|
||||
Trello makes it possible to attach links to a card. Deck does not have this feature. Attachments and attachment links are added in a markdown table at the end of the description for every imported card that has attachments in Trello.
|
||||
|
||||
* Get the API Key and API Token [here](https://developer.atlassian.com/cloud/trello/guides/rest-api/api-introduction/#authentication-and-authorization)
|
||||
* Get the ID of the board you want to import by making a request to:
|
||||
https://api.trello.com/1/members/me/boards?key={yourKey}&token={yourToken}&fields=id,name
|
||||
|
||||
This ID you will use in the configuration file in the `board` property
|
||||
* Create the configuration file
|
||||
|
||||
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/main/lib/Service/Importer/fixtures/config-trelloApi-schema.json) for import `Trello JSON`
|
||||
|
||||
Example configuration file:
|
||||
```json
|
||||
{
|
||||
"owner": "admin",
|
||||
"color": "0800fd",
|
||||
"api": {
|
||||
"key": "0cc175b9c0f1b6a831c399e269772661",
|
||||
"token": "92eb5ffee6ae2fec3ad71c777531578f4a8a08f09d37b73795649038408b5f33"
|
||||
},
|
||||
"board": "8277e0910d750195b4487976",
|
||||
"uidRelation": {
|
||||
"johndoe": "johndoe"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -25,17 +25,15 @@ namespace OCA\Deck\Command;
|
||||
|
||||
use OCA\Deck\Service\Importer\BoardImportCommandService;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class BoardImport extends Command {
|
||||
private BoardImportCommandService $boardImportCommandService;
|
||||
|
||||
public function __construct(
|
||||
BoardImportCommandService $boardImportCommandService
|
||||
private BoardImportCommandService $boardImportCommandService
|
||||
) {
|
||||
$this->boardImportCommandService = $boardImportCommandService;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@@ -44,7 +42,9 @@ class BoardImport extends Command {
|
||||
*/
|
||||
protected function configure() {
|
||||
$allowedSystems = $this->boardImportCommandService->getAllowedImportSystems();
|
||||
$names = array_column($allowedSystems, 'name');
|
||||
$names = array_map(function ($name) {
|
||||
return '"' . $name . '"';
|
||||
}, array_column($allowedSystems, 'internalName'));
|
||||
$this
|
||||
->setName('deck:import')
|
||||
->setDescription('Import data')
|
||||
@@ -53,7 +53,7 @@ class BoardImport extends Command {
|
||||
null,
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'Source system for import. Available options: ' . implode(', ', $names) . '.',
|
||||
null
|
||||
'DeckJson',
|
||||
)
|
||||
->addOption(
|
||||
'config',
|
||||
@@ -69,6 +69,11 @@ class BoardImport extends Command {
|
||||
'Data file to import.',
|
||||
'data.json'
|
||||
)
|
||||
->addArgument(
|
||||
'file',
|
||||
InputArgument::OPTIONAL,
|
||||
'File to import',
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,39 +29,23 @@ use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Db\StackMapper;
|
||||
use OCA\Deck\Model\CardDetails;
|
||||
use OCA\Deck\Service\BoardService;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IUserManager;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\DB\Exception;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class UserExport extends Command {
|
||||
protected $boardService;
|
||||
protected $cardMapper;
|
||||
private $userManager;
|
||||
private $groupManager;
|
||||
private $assignedUsersMapper;
|
||||
|
||||
public function __construct(BoardMapper $boardMapper,
|
||||
BoardService $boardService,
|
||||
StackMapper $stackMapper,
|
||||
CardMapper $cardMapper,
|
||||
AssignmentMapper $assignedUsersMapper,
|
||||
IUserManager $userManager,
|
||||
IGroupManager $groupManager) {
|
||||
public function __construct(
|
||||
private IAppManager $appManager,
|
||||
private BoardMapper $boardMapper,
|
||||
private BoardService $boardService,
|
||||
private StackMapper $stackMapper,
|
||||
private CardMapper $cardMapper,
|
||||
private AssignmentMapper $assignedUsersMapper,
|
||||
) {
|
||||
parent::__construct();
|
||||
|
||||
$this->cardMapper = $cardMapper;
|
||||
$this->boardService = $boardService;
|
||||
$this->stackMapper = $stackMapper;
|
||||
$this->assignedUsersMapper = $assignedUsersMapper;
|
||||
$this->boardMapper = $boardMapper;
|
||||
|
||||
$this->userManager = $userManager;
|
||||
$this->groupManager = $groupManager;
|
||||
}
|
||||
|
||||
protected function configure() {
|
||||
@@ -73,30 +57,27 @@ class UserExport extends Command {
|
||||
InputArgument::REQUIRED,
|
||||
'User ID of the user'
|
||||
)
|
||||
->addOption('legacy-format', 'l')
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @return int
|
||||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
* @throws \ReflectionException
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
$userId = $input->getArgument('user-id');
|
||||
$legacyFormat = $input->getOption('legacy-format');
|
||||
|
||||
$this->boardService->setUserId($userId);
|
||||
$boards = $this->boardService->findAll();
|
||||
$boards = $this->boardService->findAll(fullDetails: false);
|
||||
|
||||
$data = [];
|
||||
foreach ($boards as $board) {
|
||||
$fullBoard = $this->boardMapper->find($board->getId(), true, true);
|
||||
$data[$board->getId()] = (array)$fullBoard->jsonSerialize();
|
||||
$data[$board->getId()] = $fullBoard->jsonSerialize();
|
||||
$stacks = $this->stackMapper->findAll($board->getId());
|
||||
foreach ($stacks as $stack) {
|
||||
$data[$board->getId()]['stacks'][] = (array)$stack->jsonSerialize();
|
||||
$data[$board->getId()]['stacks'][$stack->getId()] = $stack->jsonSerialize();
|
||||
$cards = $this->cardMapper->findAllByStack($stack->getId());
|
||||
foreach ($cards as $card) {
|
||||
$fullCard = $this->cardMapper->find($card->getId());
|
||||
@@ -108,7 +89,12 @@ class UserExport extends Command {
|
||||
}
|
||||
}
|
||||
}
|
||||
$output->writeln(json_encode($data, JSON_PRETTY_PRINT));
|
||||
$output->writeln(json_encode(
|
||||
$legacyFormat ? $data : [
|
||||
'version' => $this->appManager->getAppVersion('deck'),
|
||||
'boards' => $data
|
||||
],
|
||||
JSON_PRETTY_PRINT));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,4 +41,17 @@ class Assignment extends RelationalEntity implements JsonSerializable {
|
||||
$this->addType('type', 'integer');
|
||||
$this->addResolvable('participant');
|
||||
}
|
||||
|
||||
public function getTypeString(): string {
|
||||
switch ($this->getType()) {
|
||||
case self::TYPE_USER:
|
||||
return 'user';
|
||||
case self::TYPE_GROUP:
|
||||
return 'group';
|
||||
case self::TYPE_CIRCLE:
|
||||
return 'circle';
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,12 +532,12 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
if ($boardId) {
|
||||
unset($this->boardCache[$boardId]);
|
||||
} else {
|
||||
$this->boardCache = null;
|
||||
$this->boardCache = new CappedMemoryCache();
|
||||
}
|
||||
if ($userId) {
|
||||
unset($this->userBoardCache[$userId]);
|
||||
} else {
|
||||
$this->userBoardCache = null;
|
||||
$this->userBoardCache = new CappedMemoryCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,9 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
|
||||
}
|
||||
|
||||
public function insert(Entity $entity): Entity {
|
||||
if (!in_array('lastModified', $entity->getUpdatedFields())) {
|
||||
$entity->setLastModified(time());
|
||||
}
|
||||
return parent::insert($entity);
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ class RelationalEntity extends Entity implements \JsonSerializable {
|
||||
|
||||
$attr = lcfirst(substr($methodName, 3));
|
||||
if (array_key_exists($attr, $this->_resolvedProperties) && strpos($methodName, 'set') === 0) {
|
||||
if (!is_scalar($args[0])) {
|
||||
if ($args[0] !== null && !is_scalar($args[0])) {
|
||||
$args[0] = $args[0]['primaryKey'];
|
||||
}
|
||||
parent::setter($attr, $args);
|
||||
|
||||
@@ -61,6 +61,10 @@ abstract class ABoardImportService {
|
||||
*/
|
||||
abstract public function bootstrap(): void;
|
||||
|
||||
public function getBoards(): array {
|
||||
return [$this->getImportService()->getData()];
|
||||
}
|
||||
|
||||
abstract public function getBoard(): ?Board;
|
||||
|
||||
/**
|
||||
@@ -133,4 +137,13 @@ abstract class ABoardImportService {
|
||||
public function needValidateData(): bool {
|
||||
return $this->needValidateData;
|
||||
}
|
||||
|
||||
public function reset(): void {
|
||||
// FIXME: Would be cleaner if we could just get a new instance per board
|
||||
// but currently https://github.com/nextcloud/deck/blob/7d820aa3f9fc69ada8188549b9a2fbb9093ffb95/lib/Service/Importer/BoardImportService.php#L194 returns a singleton
|
||||
$this->labels = [];
|
||||
$this->stacks = [];
|
||||
$this->acls = [];
|
||||
$this->cards = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,10 @@ class BoardImportCommandService extends BoardImportService {
|
||||
protected function validateConfig(): void {
|
||||
try {
|
||||
$config = $this->getInput()->getOption('config');
|
||||
if (!$config) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_string($config)) {
|
||||
if (!is_file($config)) {
|
||||
throw new NotFoundException('It\'s not a valid config file.');
|
||||
@@ -95,7 +99,7 @@ class BoardImportCommandService extends BoardImportService {
|
||||
$helper = $this->getCommand()->getHelper('question');
|
||||
$question = new Question(
|
||||
"<info>You can get more info on https://deck.readthedocs.io/en/latest/User_documentation_en/#6-import-boards</info>\n" .
|
||||
'Please inform a valid config json file: ',
|
||||
'Please provide a valid config json file: ',
|
||||
'config.json'
|
||||
);
|
||||
$question->setValidator(function (string $answer) {
|
||||
@@ -130,7 +134,7 @@ class BoardImportCommandService extends BoardImportService {
|
||||
$allowedSystems = $this->getAllowedImportSystems();
|
||||
$names = array_column($allowedSystems, 'name');
|
||||
$question = new ChoiceQuestion(
|
||||
'Please inform a source system',
|
||||
'Please select a source system',
|
||||
$names,
|
||||
0
|
||||
);
|
||||
@@ -145,6 +149,18 @@ class BoardImportCommandService extends BoardImportService {
|
||||
if (!$this->getImportSystem()->needValidateData()) {
|
||||
return;
|
||||
}
|
||||
$data = $this->getInput()->getArgument('file');
|
||||
if (is_string($data)) {
|
||||
if (!file_exists($data)) {
|
||||
throw new \OCP\Files\NotFoundException('Could not find file ' . $data);
|
||||
}
|
||||
$data = json_decode(file_get_contents($data));
|
||||
if ($data instanceof \stdClass) {
|
||||
$this->setData($data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$data = $this->getInput()->getOption('data');
|
||||
if (is_string($data)) {
|
||||
$data = json_decode(file_get_contents($data));
|
||||
@@ -174,12 +190,37 @@ class BoardImportCommandService extends BoardImportService {
|
||||
public function bootstrap(): void {
|
||||
$this->setSystem($this->getInput()->getOption('system'));
|
||||
parent::bootstrap();
|
||||
|
||||
$this->registerErrorCollector(function ($error, $exception) {
|
||||
$message = $error;
|
||||
if ($exception instanceof \Throwable) {
|
||||
$message .= ': ' . $exception->getMessage();
|
||||
}
|
||||
$this->getOutput()->writeln('<error>' . $message . '</error>');
|
||||
if ($exception instanceof \Throwable && $this->getOutput()->isVeryVerbose()) {
|
||||
$this->getOutput()->writeln($exception->getTraceAsString());
|
||||
}
|
||||
});
|
||||
|
||||
$this->registerOutputCollector(function ($info) {
|
||||
if ($this->getOutput()->isVerbose()) {
|
||||
$this->getOutput()->writeln('<info>' . $info . '</info>', );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function import(): void {
|
||||
$this->getOutput()->writeln('Starting import...');
|
||||
$this->bootstrap();
|
||||
$this->getOutput()->writeln('Importing board...');
|
||||
$this->validateSystem();
|
||||
$this->validateConfig();
|
||||
$boards = $this->getImportSystem()->getBoards();
|
||||
|
||||
foreach ($boards as $board) {
|
||||
try {
|
||||
$this->reset();
|
||||
$this->setData($board);
|
||||
$this->getOutput()->writeln('Importing board "' . $board->title . '".');
|
||||
$this->importBoard();
|
||||
$this->getOutput()->writeln('Assign users to board...');
|
||||
$this->importAcl();
|
||||
@@ -195,5 +236,10 @@ class BoardImportCommandService extends BoardImportService {
|
||||
$this->importComments();
|
||||
$this->getOutput()->writeln('Importing participants...');
|
||||
$this->importCardAssignments();
|
||||
$this->getOutput()->writeln('<info>Finished board import of "' . $this->getBoard()->getTitle() . '"</info>');
|
||||
} catch (\Exception $e) {
|
||||
$this->output->writeln('<error>Import failed for board ' . $board->title . ': ' . $e->getMessage() . '</error>');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ use OCA\Deck\Event\BoardImportGetAllowedEvent;
|
||||
use OCA\Deck\Exceptions\ConflictException;
|
||||
use OCA\Deck\NotFoundException;
|
||||
use OCA\Deck\Service\FileService;
|
||||
use OCA\Deck\Service\Importer\Systems\DeckJsonService;
|
||||
use OCA\Deck\Service\Importer\Systems\TrelloApiService;
|
||||
use OCA\Deck\Service\Importer\Systems\TrelloJsonService;
|
||||
use OCP\Comments\IComment;
|
||||
@@ -48,20 +49,11 @@ use OCP\Comments\NotFoundException as CommentNotFoundException;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Server;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class BoardImportService {
|
||||
private IUserManager $userManager;
|
||||
private BoardMapper $boardMapper;
|
||||
private AclMapper $aclMapper;
|
||||
private LabelMapper $labelMapper;
|
||||
private StackMapper $stackMapper;
|
||||
private CardMapper $cardMapper;
|
||||
private AssignmentMapper $assignmentMapper;
|
||||
private AttachmentMapper $attachmentMapper;
|
||||
private ICommentsManager $commentsManager;
|
||||
private IEventDispatcher $eventDispatcher;
|
||||
private string $system = '';
|
||||
private ?ABoardImportService $systemInstance;
|
||||
private ?ABoardImportService $systemInstance = null;
|
||||
private array $allowedSystems = [];
|
||||
/**
|
||||
* Data object created from config JSON
|
||||
@@ -79,30 +71,50 @@ class BoardImportService {
|
||||
private $data;
|
||||
private Board $board;
|
||||
|
||||
/** @var callable[] */
|
||||
private array $errorCollectors = [];
|
||||
/** @var callable[] */
|
||||
private array $outputCollectors = [];
|
||||
|
||||
public function __construct(
|
||||
IUserManager $userManager,
|
||||
BoardMapper $boardMapper,
|
||||
AclMapper $aclMapper,
|
||||
LabelMapper $labelMapper,
|
||||
StackMapper $stackMapper,
|
||||
AssignmentMapper $assignmentMapper,
|
||||
AttachmentMapper $attachmentMapper,
|
||||
CardMapper $cardMapper,
|
||||
ICommentsManager $commentsManager,
|
||||
IEventDispatcher $eventDispatcher
|
||||
private IUserManager $userManager,
|
||||
private BoardMapper $boardMapper,
|
||||
private AclMapper $aclMapper,
|
||||
private LabelMapper $labelMapper,
|
||||
private StackMapper $stackMapper,
|
||||
private AssignmentMapper $assignmentMapper,
|
||||
private AttachmentMapper $attachmentMapper,
|
||||
private CardMapper $cardMapper,
|
||||
private ICommentsManager $commentsManager,
|
||||
private IEventDispatcher $eventDispatcher,
|
||||
private LoggerInterface $logger
|
||||
) {
|
||||
$this->userManager = $userManager;
|
||||
$this->boardMapper = $boardMapper;
|
||||
$this->aclMapper = $aclMapper;
|
||||
$this->labelMapper = $labelMapper;
|
||||
$this->stackMapper = $stackMapper;
|
||||
$this->cardMapper = $cardMapper;
|
||||
$this->assignmentMapper = $assignmentMapper;
|
||||
$this->attachmentMapper = $attachmentMapper;
|
||||
$this->commentsManager = $commentsManager;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->board = new Board();
|
||||
$this->disableCommentsEvents();
|
||||
|
||||
$this->config = new \stdClass();
|
||||
}
|
||||
|
||||
public function registerErrorCollector(callable $errorCollector): void {
|
||||
$this->errorCollectors[] = $errorCollector;
|
||||
}
|
||||
|
||||
public function registerOutputCollector(callable $outputCollector): void {
|
||||
$this->outputCollectors[] = $outputCollector;
|
||||
}
|
||||
|
||||
private function addError(string $message, $exception): void {
|
||||
$message .= ' (on board ' . $this->getBoard()->getTitle() . ')';
|
||||
foreach ($this->errorCollectors as $errorCollector) {
|
||||
$errorCollector($message, $exception);
|
||||
}
|
||||
$this->logger->error($message, ['exception' => $exception]);
|
||||
}
|
||||
|
||||
private function addOutput(string $message): void {
|
||||
foreach ($this->outputCollectors as $outputCollector) {
|
||||
$outputCollector($message);
|
||||
}
|
||||
}
|
||||
|
||||
private function disableCommentsEvents(): void {
|
||||
@@ -120,7 +132,11 @@ class BoardImportService {
|
||||
|
||||
public function import(): void {
|
||||
$this->bootstrap();
|
||||
$boards = $this->getImportSystem()->getBoards();
|
||||
foreach ($boards as $board) {
|
||||
try {
|
||||
$this->reset();
|
||||
$this->setData($board);
|
||||
$this->importBoard();
|
||||
$this->importAcl();
|
||||
$this->importLabels();
|
||||
@@ -130,15 +146,17 @@ class BoardImportService {
|
||||
$this->importComments();
|
||||
$this->importCardAssignments();
|
||||
} catch (\Throwable $th) {
|
||||
$this->logger->error('Failed to import board', ['exception' => $th]);
|
||||
throw new BadRequestException($th->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function validateSystem(): void {
|
||||
$allowedSystems = $this->getAllowedImportSystems();
|
||||
$allowedSystems = array_column($allowedSystems, 'internalName');
|
||||
if (!in_array($this->getSystem(), $allowedSystems)) {
|
||||
throw new NotFoundException('Invalid system');
|
||||
throw new NotFoundException('Invalid system: ' . $this->getSystem());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +182,11 @@ class BoardImportService {
|
||||
|
||||
public function getAllowedImportSystems(): array {
|
||||
if (!$this->allowedSystems) {
|
||||
$this->addAllowedImportSystem([
|
||||
'name' => DeckJsonService::$name,
|
||||
'class' => DeckJsonService::class,
|
||||
'internalName' => 'DeckJson'
|
||||
]);
|
||||
$this->addAllowedImportSystem([
|
||||
'name' => TrelloApiService::$name,
|
||||
'class' => TrelloApiService::class,
|
||||
@@ -195,8 +218,17 @@ class BoardImportService {
|
||||
$this->systemInstance = $instance;
|
||||
}
|
||||
|
||||
public function reset(): void {
|
||||
$this->board = new Board();
|
||||
$this->getImportSystem()->reset();
|
||||
}
|
||||
|
||||
public function importBoard(): void {
|
||||
$board = $this->getImportSystem()->getBoard();
|
||||
if (!$this->userManager->userExists($board->getOwner())) {
|
||||
throw new \Exception('Target owner ' . $board->getOwner() . ' not found. Please provide a mapping through the import config.');
|
||||
}
|
||||
|
||||
if ($board) {
|
||||
$this->boardMapper->insert($board);
|
||||
$this->board = $board;
|
||||
@@ -213,8 +245,12 @@ class BoardImportService {
|
||||
public function importAcl(): void {
|
||||
$aclList = $this->getImportSystem()->getAclList();
|
||||
foreach ($aclList as $code => $acl) {
|
||||
try {
|
||||
$this->aclMapper->insert($acl);
|
||||
$this->getImportSystem()->updateAcl($code, $acl);
|
||||
} catch (\Exception $e) {
|
||||
$this->addError('Failed to import acl rule for ' . $acl->getParticipant(), $e);
|
||||
}
|
||||
}
|
||||
$this->getBoard()->setAcl($aclList);
|
||||
}
|
||||
@@ -222,8 +258,12 @@ class BoardImportService {
|
||||
public function importLabels(): void {
|
||||
$labels = $this->getImportSystem()->getLabels();
|
||||
foreach ($labels as $code => $label) {
|
||||
try {
|
||||
$this->labelMapper->insert($label);
|
||||
$this->getImportSystem()->updateLabel($code, $label);
|
||||
} catch (\Exception $e) {
|
||||
$this->addError('Failed to import label ' . $label->getTitle(), $e);
|
||||
}
|
||||
}
|
||||
$this->getBoard()->setLabels($labels);
|
||||
}
|
||||
@@ -231,8 +271,12 @@ class BoardImportService {
|
||||
public function importStacks(): void {
|
||||
$stacks = $this->getImportSystem()->getStacks();
|
||||
foreach ($stacks as $code => $stack) {
|
||||
try {
|
||||
$this->stackMapper->insert($stack);
|
||||
$this->getImportSystem()->updateStack($code, $stack);
|
||||
} catch (\Exception $e) {
|
||||
$this->addError('Failed to import list ' . $stack->getTitle(), $e);
|
||||
}
|
||||
}
|
||||
$this->getBoard()->setStacks(array_values($stacks));
|
||||
}
|
||||
@@ -240,6 +284,7 @@ class BoardImportService {
|
||||
public function importCards(): void {
|
||||
$cards = $this->getImportSystem()->getCards();
|
||||
foreach ($cards as $code => $card) {
|
||||
try {
|
||||
$createdAt = $card->getCreatedAt();
|
||||
$lastModified = $card->getLastModified();
|
||||
$this->cardMapper->insert($card);
|
||||
@@ -256,6 +301,9 @@ class BoardImportService {
|
||||
$this->cardMapper->update($card, false);
|
||||
}
|
||||
$this->getImportSystem()->updateCard($code, $card);
|
||||
} catch (\Exception $e) {
|
||||
$this->addError('Failed to import card ' . $card->getTitle(), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,11 +324,15 @@ class BoardImportService {
|
||||
$data = $this->getImportSystem()->getCardLabelAssignment();
|
||||
foreach ($data as $cardId => $assignemnt) {
|
||||
foreach ($assignemnt as $assignmentId => $labelId) {
|
||||
try {
|
||||
$this->assignCardToLabel(
|
||||
$cardId,
|
||||
$labelId
|
||||
);
|
||||
$this->getImportSystem()->updateCardLabelsAssignment($cardId, $assignmentId, $labelId);
|
||||
} catch (\Exception $e) {
|
||||
$this->addError('Failed to assign label ' . $labelId . ' to ' . $cardId, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,9 +374,14 @@ class BoardImportService {
|
||||
public function importCardAssignments(): void {
|
||||
$allAssignments = $this->getImportSystem()->getCardAssignments();
|
||||
foreach ($allAssignments as $cardId => $assignments) {
|
||||
foreach ($assignments as $assignmentId => $assignment) {
|
||||
$this->assignmentMapper->insert($assignment);
|
||||
$this->getImportSystem()->updateCardAssignment($cardId, $assignmentId, $assignment);
|
||||
foreach ($assignments as $assignment) {
|
||||
try {
|
||||
$assignment = $this->assignmentMapper->insert($assignment);
|
||||
$this->getImportSystem()->updateCardAssignment($cardId, (string)$assignment->getId(), $assignment);
|
||||
$this->addOutput('Assignment ' . $assignment->getParticipant() . ' added');
|
||||
} catch (NotFoundException $e) {
|
||||
$this->addError('No origin or mapping found for card "' . $cardId . '" and ' . $assignment->getTypeString() .' assignment "' . $assignment->getParticipant(), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
260
lib/Service/Importer/Systems/DeckJsonService.php
Normal file
260
lib/Service/Importer/Systems/DeckJsonService.php
Normal file
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Service\Importer\Systems;
|
||||
|
||||
use OCA\Deck\BadRequestException;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\Assignment;
|
||||
use OCA\Deck\Db\Board;
|
||||
use OCA\Deck\Db\Card;
|
||||
use OCA\Deck\Db\Label;
|
||||
use OCA\Deck\Db\Stack;
|
||||
use OCA\Deck\Service\Importer\ABoardImportService;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
|
||||
class DeckJsonService extends ABoardImportService {
|
||||
public static $name = 'Deck JSON';
|
||||
/** @var IUser[] */
|
||||
private array $members = [];
|
||||
private array $tmpCards = [];
|
||||
|
||||
public function __construct(
|
||||
private IUserManager $userManager,
|
||||
) {
|
||||
}
|
||||
|
||||
public function bootstrap(): void {
|
||||
$this->validateUsers();
|
||||
}
|
||||
|
||||
public function getJsonSchemaPath(): string {
|
||||
return implode(DIRECTORY_SEPARATOR, [
|
||||
__DIR__,
|
||||
'..',
|
||||
'fixtures',
|
||||
'config-deckJson-schema.json',
|
||||
]);
|
||||
}
|
||||
|
||||
public function validateUsers(): void {
|
||||
$relation = $this->getImportService()->getConfig('uidRelation');
|
||||
if (empty($relation)) {
|
||||
return;
|
||||
}
|
||||
foreach ($relation as $exportUid => $nextcloudUid) {
|
||||
if (!is_string($nextcloudUid) && !is_numeric($nextcloudUid)) {
|
||||
throw new \LogicException('User on setting uidRelation is invalid');
|
||||
}
|
||||
$nextcloudUid = (string) $nextcloudUid;
|
||||
$this->getImportService()->getConfig('uidRelation')->$exportUid = $this->userManager->get($nextcloudUid);
|
||||
if (!$this->getImportService()->getConfig('uidRelation')->$exportUid) {
|
||||
throw new \LogicException('User on setting uidRelation not found: ' . $nextcloudUid);
|
||||
}
|
||||
$this->members[$exportUid] = $this->getImportService()->getConfig('uidRelation')->$exportUid;
|
||||
}
|
||||
}
|
||||
|
||||
public function mapMember($uid): ?string {
|
||||
$ownerMap = $this->mapOwner($uid);
|
||||
$sourceId = ($this->getImportService()->getData()->owner->primaryKey ?? $this->getImportService()->getData()->owner);
|
||||
|
||||
if ($uid === $sourceId && $ownerMap !== $sourceId) {
|
||||
return $ownerMap;
|
||||
}
|
||||
|
||||
$uidCandidate = isset($this->members[$uid]) ? $this->members[$uid]?->getUID() ?? null : null;
|
||||
if ($uidCandidate) {
|
||||
return $uidCandidate;
|
||||
}
|
||||
|
||||
if ($this->userManager->userExists($uid)) {
|
||||
return $uid;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function mapOwner(string $uid): string {
|
||||
$configOwner = $this->getImportService()->getConfig('owner');
|
||||
if ($configOwner) {
|
||||
return $configOwner->getUID();
|
||||
}
|
||||
|
||||
return $uid;
|
||||
}
|
||||
|
||||
public function getCardAssignments(): array {
|
||||
$assignments = [];
|
||||
foreach ($this->tmpCards as $sourceCard) {
|
||||
foreach ($sourceCard->assignedUsers as $idMember) {
|
||||
$assignment = new Assignment();
|
||||
$assignment->setCardId($this->cards[$sourceCard->id]->getId());
|
||||
$assignment->setParticipant($this->mapMember($idMember->participant->uid ?? $idMember->participant));
|
||||
$assignment->setType($idMember->participant->type);
|
||||
$assignments[$sourceCard->id][] = $assignment;
|
||||
}
|
||||
}
|
||||
return $assignments;
|
||||
}
|
||||
|
||||
public function getComments(): array {
|
||||
// Comments are not implemented in export
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getCardLabelAssignment(): array {
|
||||
$cardsLabels = [];
|
||||
foreach ($this->tmpCards as $sourceCard) {
|
||||
foreach ($sourceCard->labels as $label) {
|
||||
$cardId = $this->cards[$sourceCard->id]->getId();
|
||||
$labelId = $this->labels[$label->id]->getId();
|
||||
$cardsLabels[$cardId][] = $labelId;
|
||||
}
|
||||
}
|
||||
return $cardsLabels;
|
||||
}
|
||||
|
||||
public function getBoard(): Board {
|
||||
$board = $this->getImportService()->getBoard();
|
||||
if (empty($this->getImportService()->getData()->title)) {
|
||||
throw new BadRequestException('Invalid name of board');
|
||||
}
|
||||
$importBoard = $this->getImportService()->getData();
|
||||
$board->setTitle($importBoard->title);
|
||||
$board->setOwner($this->mapOwner($importBoard->owner?->uid ?? $importBoard->owner));
|
||||
$board->setColor($importBoard->color);
|
||||
$board->setArchived($importBoard->archived);
|
||||
$board->setDeletedAt($importBoard->deletedAt);
|
||||
$board->setLastModified($importBoard->lastModified);
|
||||
return $board;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Label[]
|
||||
*/
|
||||
public function getLabels(): array {
|
||||
foreach ($this->getImportService()->getData()->labels as $label) {
|
||||
$newLabel = new Label();
|
||||
$newLabel->setTitle($label->title);
|
||||
$newLabel->setColor($label->color);
|
||||
$newLabel->setBoardId($this->getImportService()->getBoard()->getId());
|
||||
$newLabel->setLastModified($label->lastModified);
|
||||
$this->labels[$label->id] = $newLabel;
|
||||
}
|
||||
return $this->labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Stack[]
|
||||
*/
|
||||
public function getStacks(): array {
|
||||
$return = [];
|
||||
foreach ($this->getImportService()->getData()->stacks as $index => $source) {
|
||||
if ($source->title) {
|
||||
$stack = new Stack();
|
||||
$stack->setTitle($source->title);
|
||||
$stack->setBoardId($this->getImportService()->getBoard()->getId());
|
||||
$stack->setOrder($source->order);
|
||||
$stack->setLastModified($source->lastModified);
|
||||
$return[$source->id] = $stack;
|
||||
}
|
||||
|
||||
if (isset($source->cards)) {
|
||||
foreach ($source->cards as $card) {
|
||||
$card->stackId = $index;
|
||||
$this->tmpCards[] = $card;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Card[]
|
||||
*/
|
||||
public function getCards(): array {
|
||||
$cards = [];
|
||||
foreach ($this->tmpCards as $cardSource) {
|
||||
$card = new Card();
|
||||
$card->setTitle($cardSource->title);
|
||||
$card->setLastModified($cardSource->lastModified);
|
||||
$card->setLastEditor($cardSource->lastEditor);
|
||||
$card->setCreatedAt($cardSource->createdAt);
|
||||
$card->setArchived($cardSource->archived);
|
||||
$card->setDescription($cardSource->description);
|
||||
$card->setStackId($this->stacks[$cardSource->stackId]->getId());
|
||||
$card->setType('plain');
|
||||
$card->setOrder($cardSource->order);
|
||||
$boardOwner = $this->getBoard()->getOwner();
|
||||
$card->setOwner($this->mapOwner(is_string($boardOwner) ? $boardOwner : $boardOwner->getUID()));
|
||||
$card->setDuedate($cardSource->duedate);
|
||||
$cards[$cardSource->id] = $card;
|
||||
}
|
||||
return $cards;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Acl[]
|
||||
*/
|
||||
public function getAclList(): array {
|
||||
$board = $this->getImportService()->getData();
|
||||
$return = [];
|
||||
foreach ($board->acl as $aclData) {
|
||||
$acl = new Acl();
|
||||
$acl->setBoardId($this->getImportService()->getBoard()->getId());
|
||||
$acl->setType($aclData->type);
|
||||
$participant = $aclData->participant?->primaryKey ?? $aclData->participant;
|
||||
if ($acl->getType() === Acl::PERMISSION_TYPE_USER) {
|
||||
$participant = $this->mapMember($participant);
|
||||
}
|
||||
$acl->setParticipant($participant);
|
||||
$acl->setPermissionEdit($aclData->permissionEdit);
|
||||
$acl->setPermissionShare($aclData->permissionShare);
|
||||
$acl->setPermissionManage($aclData->permissionManage);
|
||||
if ($participant) {
|
||||
$return[] = $acl;
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function replaceUsernames(string $text): string {
|
||||
foreach ($this->getImportService()->getConfig('uidRelation') as $trello => $nextcloud) {
|
||||
$text = str_replace($trello, $nextcloud->getUID(), $text);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
public function getBoards(): array {
|
||||
// Old format has just the raw board data, new one a key boards
|
||||
$data = $this->getImportService()->getData();
|
||||
return array_values((array)($data->boards ?? $data));
|
||||
}
|
||||
|
||||
public function reset(): void {
|
||||
parent::reset();
|
||||
$this->tmpCards = [];
|
||||
}
|
||||
}
|
||||
@@ -397,4 +397,12 @@ class TrelloJsonService extends ABoardImportService {
|
||||
$trelloCard->desc .= "| [{$name}]({$attachment->url}) | {$attachment->date} |\n";
|
||||
}
|
||||
}
|
||||
|
||||
public function getBoards(): array {
|
||||
if ($this->getImportService()->getData()->boards) {
|
||||
return $this->getImportService()->getData()->boards;
|
||||
}
|
||||
|
||||
return [$this->getImportService()->getData()];
|
||||
}
|
||||
}
|
||||
|
||||
17
lib/Service/Importer/fixtures/config-deckJson-schema.json
Normal file
17
lib/Service/Importer/fixtures/config-deckJson-schema.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uidRelation": {
|
||||
"type": "object",
|
||||
"comment": "Relationship between Trello and Nextcloud usernames",
|
||||
"example": {
|
||||
"johndoe": "admin"
|
||||
}
|
||||
},
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"comment": "Nextcloud owner username"
|
||||
}
|
||||
}
|
||||
}
|
||||
7
tests/data/config-deckJson.json
Normal file
7
tests/data/config-deckJson.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"owner": "admin",
|
||||
"color": "0800fd",
|
||||
"uidRelation": {
|
||||
"johndoe": "test-user-1"
|
||||
}
|
||||
}
|
||||
748
tests/data/deck.json
Normal file
748
tests/data/deck.json
Normal file
@@ -0,0 +1,748 @@
|
||||
{
|
||||
"version": "1.11.0-dev",
|
||||
"boards": {
|
||||
"188": {
|
||||
"id": 188,
|
||||
"title": "My test board",
|
||||
"owner": {
|
||||
"primaryKey": "admin",
|
||||
"uid": "admin",
|
||||
"displayname": "admin",
|
||||
"type": 0
|
||||
},
|
||||
"color": "e0ed31",
|
||||
"archived": false,
|
||||
"labels": [
|
||||
{
|
||||
"id": 239,
|
||||
"title": "L2",
|
||||
"color": "31CC7C",
|
||||
"boardId": 188,
|
||||
"cardId": null,
|
||||
"lastModified": 1689667435,
|
||||
"ETag": "63b77251cca5a56fe74a97e4baeab59c"
|
||||
},
|
||||
{
|
||||
"id": 240,
|
||||
"title": "L4",
|
||||
"color": "317CCC",
|
||||
"boardId": 188,
|
||||
"cardId": null,
|
||||
"lastModified": 1689667442,
|
||||
"ETag": "15dcabeb47583ce5398faaeb65f7a4b6"
|
||||
},
|
||||
{
|
||||
"id": 241,
|
||||
"title": "L1",
|
||||
"color": "FF7A66",
|
||||
"boardId": 188,
|
||||
"cardId": null,
|
||||
"lastModified": 1689667432,
|
||||
"ETag": "7d58be91f19ebc4f94b352db8c76c056"
|
||||
},
|
||||
{
|
||||
"id": 242,
|
||||
"title": "L3",
|
||||
"color": "F1DB50",
|
||||
"boardId": 188,
|
||||
"cardId": null,
|
||||
"lastModified": 1689667440,
|
||||
"ETag": "160253b9d33ae0a7a3af90e7d418ba60"
|
||||
}
|
||||
],
|
||||
"acl": [],
|
||||
"permissions": {
|
||||
"PERMISSION_READ": true,
|
||||
"PERMISSION_EDIT": true,
|
||||
"PERMISSION_MANAGE": true,
|
||||
"PERMISSION_SHARE": true
|
||||
},
|
||||
"users": [],
|
||||
"shared": 0,
|
||||
"stacks": {
|
||||
"64": {
|
||||
"id": 64,
|
||||
"title": "A",
|
||||
"boardId": 188,
|
||||
"deletedAt": 0,
|
||||
"lastModified": 1689667779,
|
||||
"order": 999,
|
||||
"ETag": "ddfd0c27e53d8db94ac5e9aaa021746e",
|
||||
"cards": [
|
||||
{
|
||||
"id": 114,
|
||||
"title": "1",
|
||||
"description": "",
|
||||
"stackId": 64,
|
||||
"type": "plain",
|
||||
"lastModified": 1689667779,
|
||||
"lastEditor": null,
|
||||
"createdAt": 1689667569,
|
||||
"labels": [
|
||||
{
|
||||
"id": 239,
|
||||
"title": "L2",
|
||||
"color": "31CC7C",
|
||||
"boardId": 188,
|
||||
"cardId": 114,
|
||||
"lastModified": 1689667435,
|
||||
"ETag": "63b77251cca5a56fe74a97e4baeab59c"
|
||||
}
|
||||
],
|
||||
"assignedUsers": [],
|
||||
"attachments": null,
|
||||
"attachmentCount": null,
|
||||
"owner": {
|
||||
"primaryKey": "admin",
|
||||
"uid": "admin",
|
||||
"displayname": "admin",
|
||||
"type": 0
|
||||
},
|
||||
"order": 999,
|
||||
"archived": false,
|
||||
"duedate": "2050-07-24T22:00:00+00:00",
|
||||
"deletedAt": 0,
|
||||
"commentsUnread": 0,
|
||||
"commentsCount": 0,
|
||||
"ETag": "ddfd0c27e53d8db94ac5e9aaa021746e",
|
||||
"overdue": 0,
|
||||
"boardId": 188,
|
||||
"board": {
|
||||
"id": 188,
|
||||
"title": "My test board"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 115,
|
||||
"title": "2",
|
||||
"description": "",
|
||||
"stackId": 64,
|
||||
"type": "plain",
|
||||
"lastModified": 1689667752,
|
||||
"lastEditor": null,
|
||||
"createdAt": 1689667572,
|
||||
"labels": [],
|
||||
"assignedUsers": [],
|
||||
"attachments": null,
|
||||
"attachmentCount": null,
|
||||
"owner": {
|
||||
"primaryKey": "admin",
|
||||
"uid": "admin",
|
||||
"displayname": "admin",
|
||||
"type": 0
|
||||
},
|
||||
"order": 999,
|
||||
"archived": false,
|
||||
"duedate": "2023-07-17T02:00:00+00:00",
|
||||
"deletedAt": 0,
|
||||
"commentsUnread": 0,
|
||||
"commentsCount": 0,
|
||||
"ETag": "9a8ed495f7d83f8310ae6291d6dc4624",
|
||||
"overdue": 3,
|
||||
"boardId": 188,
|
||||
"board": {
|
||||
"id": 188,
|
||||
"title": "My test board"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 116,
|
||||
"title": "3",
|
||||
"description": "",
|
||||
"stackId": 64,
|
||||
"type": "plain",
|
||||
"lastModified": 1689667760,
|
||||
"lastEditor": "admin",
|
||||
"createdAt": 1689667576,
|
||||
"labels": [],
|
||||
"assignedUsers": [
|
||||
{
|
||||
"id": 5,
|
||||
"participant": {
|
||||
"primaryKey": "admin",
|
||||
"uid": "admin",
|
||||
"displayname": "admin",
|
||||
"type": 0
|
||||
},
|
||||
"cardId": 116,
|
||||
"type": 0
|
||||
}
|
||||
],
|
||||
"attachments": null,
|
||||
"attachmentCount": null,
|
||||
"owner": {
|
||||
"primaryKey": "admin",
|
||||
"uid": "admin",
|
||||
"displayname": "admin",
|
||||
"type": 0
|
||||
},
|
||||
"order": 999,
|
||||
"archived": false,
|
||||
"duedate": null,
|
||||
"deletedAt": 0,
|
||||
"commentsUnread": 0,
|
||||
"commentsCount": 0,
|
||||
"ETag": "f908c4359e9ca0703f50da2bbe967594",
|
||||
"overdue": 0,
|
||||
"boardId": 188,
|
||||
"board": {
|
||||
"id": 188,
|
||||
"title": "My test board"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"65": {
|
||||
"id": 65,
|
||||
"title": "B",
|
||||
"boardId": 188,
|
||||
"deletedAt": 0,
|
||||
"lastModified": 1689667796,
|
||||
"order": 999,
|
||||
"ETag": "b97a2b19e1cafc8f95e3f4db71097214",
|
||||
"cards": [
|
||||
{
|
||||
"id": 117,
|
||||
"title": "4",
|
||||
"description": "",
|
||||
"stackId": 65,
|
||||
"type": "plain",
|
||||
"lastModified": 1689667767,
|
||||
"lastEditor": "admin",
|
||||
"createdAt": 1689667578,
|
||||
"labels": [
|
||||
{
|
||||
"id": 239,
|
||||
"title": "L2",
|
||||
"color": "31CC7C",
|
||||
"boardId": 188,
|
||||
"cardId": 117,
|
||||
"lastModified": 1689667435,
|
||||
"ETag": "63b77251cca5a56fe74a97e4baeab59c"
|
||||
},
|
||||
{
|
||||
"id": 240,
|
||||
"title": "L4",
|
||||
"color": "317CCC",
|
||||
"boardId": 188,
|
||||
"cardId": 117,
|
||||
"lastModified": 1689667442,
|
||||
"ETag": "15dcabeb47583ce5398faaeb65f7a4b6"
|
||||
},
|
||||
{
|
||||
"id": 241,
|
||||
"title": "L1",
|
||||
"color": "FF7A66",
|
||||
"boardId": 188,
|
||||
"cardId": 117,
|
||||
"lastModified": 1689667432,
|
||||
"ETag": "7d58be91f19ebc4f94b352db8c76c056"
|
||||
}
|
||||
],
|
||||
"assignedUsers": [],
|
||||
"attachments": null,
|
||||
"attachmentCount": null,
|
||||
"owner": {
|
||||
"primaryKey": "admin",
|
||||
"uid": "admin",
|
||||
"displayname": "admin",
|
||||
"type": 0
|
||||
},
|
||||
"order": 999,
|
||||
"archived": false,
|
||||
"duedate": null,
|
||||
"deletedAt": 0,
|
||||
"commentsUnread": 0,
|
||||
"commentsCount": 0,
|
||||
"ETag": "6b20cc46fa5d2e5f65251526b50cc130",
|
||||
"overdue": 0,
|
||||
"boardId": 188,
|
||||
"board": {
|
||||
"id": 188,
|
||||
"title": "My test board"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 118,
|
||||
"title": "5",
|
||||
"description": "",
|
||||
"stackId": 65,
|
||||
"type": "plain",
|
||||
"lastModified": 1689667773,
|
||||
"lastEditor": "admin",
|
||||
"createdAt": 1689667581,
|
||||
"labels": [
|
||||
{
|
||||
"id": 239,
|
||||
"title": "L2",
|
||||
"color": "31CC7C",
|
||||
"boardId": 188,
|
||||
"cardId": 118,
|
||||
"lastModified": 1689667435,
|
||||
"ETag": "63b77251cca5a56fe74a97e4baeab59c"
|
||||
}
|
||||
],
|
||||
"assignedUsers": [],
|
||||
"attachments": null,
|
||||
"attachmentCount": null,
|
||||
"owner": {
|
||||
"primaryKey": "admin",
|
||||
"uid": "admin",
|
||||
"displayname": "admin",
|
||||
"type": 0
|
||||
},
|
||||
"order": 999,
|
||||
"archived": false,
|
||||
"duedate": null,
|
||||
"deletedAt": 0,
|
||||
"commentsUnread": 0,
|
||||
"commentsCount": 0,
|
||||
"ETag": "488145982535a91d9ab47db647ecf539",
|
||||
"overdue": 0,
|
||||
"boardId": 188,
|
||||
"board": {
|
||||
"id": 188,
|
||||
"title": "My test board"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 119,
|
||||
"title": "6",
|
||||
"description": "# Test description\n\nHello world",
|
||||
"stackId": 65,
|
||||
"type": "plain",
|
||||
"lastModified": 1689667796,
|
||||
"lastEditor": null,
|
||||
"createdAt": 1689667583,
|
||||
"labels": [],
|
||||
"assignedUsers": [
|
||||
{
|
||||
"id": 6,
|
||||
"participant": {
|
||||
"primaryKey": "admin",
|
||||
"uid": "admin",
|
||||
"displayname": "admin",
|
||||
"type": 0
|
||||
},
|
||||
"cardId": 119,
|
||||
"type": 0
|
||||
}
|
||||
],
|
||||
"attachments": null,
|
||||
"attachmentCount": null,
|
||||
"owner": {
|
||||
"primaryKey": "admin",
|
||||
"uid": "admin",
|
||||
"displayname": "admin",
|
||||
"type": 0
|
||||
},
|
||||
"order": 999,
|
||||
"archived": false,
|
||||
"duedate": null,
|
||||
"deletedAt": 0,
|
||||
"commentsUnread": 0,
|
||||
"commentsCount": 0,
|
||||
"ETag": "b97a2b19e1cafc8f95e3f4db71097214",
|
||||
"overdue": 0,
|
||||
"boardId": 188,
|
||||
"board": {
|
||||
"id": 188,
|
||||
"title": "My test board"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"66": {
|
||||
"id": 66,
|
||||
"title": "C",
|
||||
"boardId": 188,
|
||||
"deletedAt": 0,
|
||||
"lastModified": 0,
|
||||
"order": 999,
|
||||
"ETag": "cfcd208495d565ef66e7dff9f98764da"
|
||||
}
|
||||
},
|
||||
"activeSessions": [],
|
||||
"deletedAt": 0,
|
||||
"lastModified": 1689667796,
|
||||
"settings": [],
|
||||
"ETag": "b97a2b19e1cafc8f95e3f4db71097214"
|
||||
},
|
||||
"189": {
|
||||
"id": 189,
|
||||
"title": "Shared board",
|
||||
"owner": {
|
||||
"primaryKey": "admin",
|
||||
"uid": "admin",
|
||||
"displayname": "admin",
|
||||
"type": 0
|
||||
},
|
||||
"color": "30b6d8",
|
||||
"archived": false,
|
||||
"labels": [
|
||||
{
|
||||
"id": 243,
|
||||
"title": "Finished",
|
||||
"color": "31CC7C",
|
||||
"boardId": 189,
|
||||
"cardId": null,
|
||||
"lastModified": 1689667413,
|
||||
"ETag": "aa71367f6a9a2fc2d47fc46163a30208"
|
||||
},
|
||||
{
|
||||
"id": 244,
|
||||
"title": "To review",
|
||||
"color": "317CCC",
|
||||
"boardId": 189,
|
||||
"cardId": null,
|
||||
"lastModified": 1689667413,
|
||||
"ETag": "aa71367f6a9a2fc2d47fc46163a30208"
|
||||
},
|
||||
{
|
||||
"id": 245,
|
||||
"title": "Action needed",
|
||||
"color": "FF7A66",
|
||||
"boardId": 189,
|
||||
"cardId": null,
|
||||
"lastModified": 1689667413,
|
||||
"ETag": "aa71367f6a9a2fc2d47fc46163a30208"
|
||||
},
|
||||
{
|
||||
"id": 246,
|
||||
"title": "Later",
|
||||
"color": "F1DB50",
|
||||
"boardId": 189,
|
||||
"cardId": null,
|
||||
"lastModified": 1689667413,
|
||||
"ETag": "aa71367f6a9a2fc2d47fc46163a30208"
|
||||
}
|
||||
],
|
||||
"acl": [
|
||||
{
|
||||
"id": 4,
|
||||
"participant": {
|
||||
"primaryKey": "alice",
|
||||
"uid": "alice",
|
||||
"displayname": "alice",
|
||||
"type": 0
|
||||
},
|
||||
"type": 0,
|
||||
"boardId": 189,
|
||||
"permissionEdit": true,
|
||||
"permissionShare": false,
|
||||
"permissionManage": false,
|
||||
"owner": false
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"participant": {
|
||||
"primaryKey": "jane",
|
||||
"uid": "jane",
|
||||
"displayname": "jane",
|
||||
"type": 0
|
||||
},
|
||||
"type": 0,
|
||||
"boardId": 189,
|
||||
"permissionEdit": false,
|
||||
"permissionShare": true,
|
||||
"permissionManage": false,
|
||||
"owner": false
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"participant": {
|
||||
"primaryKey": "admin",
|
||||
"uid": "admin",
|
||||
"displayname": "admin",
|
||||
"type": 1
|
||||
},
|
||||
"type": 1,
|
||||
"boardId": 189,
|
||||
"permissionEdit": false,
|
||||
"permissionShare": false,
|
||||
"permissionManage": true,
|
||||
"owner": false
|
||||
}
|
||||
],
|
||||
"permissions": {
|
||||
"PERMISSION_READ": true,
|
||||
"PERMISSION_EDIT": true,
|
||||
"PERMISSION_MANAGE": true,
|
||||
"PERMISSION_SHARE": true
|
||||
},
|
||||
"users": [],
|
||||
"shared": 0,
|
||||
"stacks": {
|
||||
"61": {
|
||||
"id": 61,
|
||||
"title": "ToDo",
|
||||
"boardId": 189,
|
||||
"deletedAt": 0,
|
||||
"lastModified": 1689667537,
|
||||
"order": 999,
|
||||
"ETag": "6c315c83f146485e6b2b6fdc24ffa617",
|
||||
"cards": [
|
||||
{
|
||||
"id": 107,
|
||||
"title": "Write tests",
|
||||
"description": "",
|
||||
"stackId": 61,
|
||||
"type": "plain",
|
||||
"lastModified": 1689667521,
|
||||
"lastEditor": null,
|
||||
"createdAt": 1689667483,
|
||||
"labels": [],
|
||||
"assignedUsers": [],
|
||||
"attachments": null,
|
||||
"attachmentCount": null,
|
||||
"owner": {
|
||||
"primaryKey": "admin",
|
||||
"uid": "admin",
|
||||
"displayname": "admin",
|
||||
"type": 0
|
||||
},
|
||||
"order": 0,
|
||||
"archived": false,
|
||||
"duedate": null,
|
||||
"deletedAt": 0,
|
||||
"commentsUnread": 0,
|
||||
"commentsCount": 0,
|
||||
"ETag": "f0450d41827f55580554c993304c8073",
|
||||
"overdue": 0,
|
||||
"boardId": 189,
|
||||
"board": {
|
||||
"id": 189,
|
||||
"title": "Shared board"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 111,
|
||||
"title": "Write blog post",
|
||||
"description": "",
|
||||
"stackId": 61,
|
||||
"type": "plain",
|
||||
"lastModified": 1689667521,
|
||||
"lastEditor": null,
|
||||
"createdAt": 1689667518,
|
||||
"labels": [],
|
||||
"assignedUsers": [],
|
||||
"attachments": null,
|
||||
"attachmentCount": null,
|
||||
"owner": {
|
||||
"primaryKey": "admin",
|
||||
"uid": "admin",
|
||||
"displayname": "admin",
|
||||
"type": 0
|
||||
},
|
||||
"order": 1,
|
||||
"archived": false,
|
||||
"duedate": null,
|
||||
"deletedAt": 0,
|
||||
"commentsUnread": 0,
|
||||
"commentsCount": 0,
|
||||
"ETag": "f0450d41827f55580554c993304c8073",
|
||||
"overdue": 0,
|
||||
"boardId": 189,
|
||||
"board": {
|
||||
"id": 189,
|
||||
"title": "Shared board"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 112,
|
||||
"title": "Announce feature",
|
||||
"description": "",
|
||||
"stackId": 61,
|
||||
"type": "plain",
|
||||
"lastModified": 1689667527,
|
||||
"lastEditor": null,
|
||||
"createdAt": 1689667527,
|
||||
"labels": [],
|
||||
"assignedUsers": [],
|
||||
"attachments": null,
|
||||
"attachmentCount": null,
|
||||
"owner": {
|
||||
"primaryKey": "admin",
|
||||
"uid": "admin",
|
||||
"displayname": "admin",
|
||||
"type": 0
|
||||
},
|
||||
"order": 999,
|
||||
"archived": false,
|
||||
"duedate": null,
|
||||
"deletedAt": 0,
|
||||
"commentsUnread": 0,
|
||||
"commentsCount": 0,
|
||||
"ETag": "1956848c45be91fefc967ee8831ea4cf",
|
||||
"overdue": 0,
|
||||
"boardId": 189,
|
||||
"board": {
|
||||
"id": 189,
|
||||
"title": "Shared board"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 113,
|
||||
"title": "\ud83c\udf89 Party",
|
||||
"description": "",
|
||||
"stackId": 61,
|
||||
"type": "plain",
|
||||
"lastModified": 1689667537,
|
||||
"lastEditor": null,
|
||||
"createdAt": 1689667537,
|
||||
"labels": [],
|
||||
"assignedUsers": [],
|
||||
"attachments": null,
|
||||
"attachmentCount": null,
|
||||
"owner": {
|
||||
"primaryKey": "admin",
|
||||
"uid": "admin",
|
||||
"displayname": "admin",
|
||||
"type": 0
|
||||
},
|
||||
"order": 999,
|
||||
"archived": false,
|
||||
"duedate": null,
|
||||
"deletedAt": 0,
|
||||
"commentsUnread": 0,
|
||||
"commentsCount": 0,
|
||||
"ETag": "6c315c83f146485e6b2b6fdc24ffa617",
|
||||
"overdue": 0,
|
||||
"boardId": 189,
|
||||
"board": {
|
||||
"id": 189,
|
||||
"title": "Shared board"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"62": {
|
||||
"id": 62,
|
||||
"title": "In progress",
|
||||
"boardId": 189,
|
||||
"deletedAt": 0,
|
||||
"lastModified": 1689667502,
|
||||
"order": 999,
|
||||
"ETag": "1498939b8816e6041da80050dacc3ed3",
|
||||
"cards": [
|
||||
{
|
||||
"id": 108,
|
||||
"title": "Write feature",
|
||||
"description": "",
|
||||
"stackId": 62,
|
||||
"type": "plain",
|
||||
"lastModified": 1689667488,
|
||||
"lastEditor": null,
|
||||
"createdAt": 1689667488,
|
||||
"labels": [],
|
||||
"assignedUsers": [],
|
||||
"attachments": null,
|
||||
"attachmentCount": null,
|
||||
"owner": {
|
||||
"primaryKey": "admin",
|
||||
"uid": "admin",
|
||||
"displayname": "admin",
|
||||
"type": 0
|
||||
},
|
||||
"order": 999,
|
||||
"archived": false,
|
||||
"duedate": null,
|
||||
"deletedAt": 0,
|
||||
"commentsUnread": 0,
|
||||
"commentsCount": 0,
|
||||
"ETag": "d2a8b634cdd96ab5ef48910bbbd715b1",
|
||||
"overdue": 0,
|
||||
"boardId": 189,
|
||||
"board": {
|
||||
"id": 189,
|
||||
"title": "Shared board"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"63": {
|
||||
"id": 63,
|
||||
"title": "Done",
|
||||
"boardId": 189,
|
||||
"deletedAt": 0,
|
||||
"lastModified": 1689667518,
|
||||
"order": 999,
|
||||
"ETag": "09ba5a39921de760db53bcd56457eea5",
|
||||
"cards": [
|
||||
{
|
||||
"id": 109,
|
||||
"title": "Plan feature",
|
||||
"description": "",
|
||||
"stackId": 63,
|
||||
"type": "plain",
|
||||
"lastModified": 1689667506,
|
||||
"lastEditor": null,
|
||||
"createdAt": 1689667493,
|
||||
"labels": [],
|
||||
"assignedUsers": [],
|
||||
"attachments": null,
|
||||
"attachmentCount": null,
|
||||
"owner": {
|
||||
"primaryKey": "admin",
|
||||
"uid": "admin",
|
||||
"displayname": "admin",
|
||||
"type": 0
|
||||
},
|
||||
"order": 0,
|
||||
"archived": false,
|
||||
"duedate": null,
|
||||
"deletedAt": 0,
|
||||
"commentsUnread": 0,
|
||||
"commentsCount": 0,
|
||||
"ETag": "193163d8a8acedbfaba196b1f0d65bc8",
|
||||
"overdue": 0,
|
||||
"boardId": 189,
|
||||
"board": {
|
||||
"id": 189,
|
||||
"title": "Shared board"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 110,
|
||||
"title": "Design feature",
|
||||
"description": "",
|
||||
"stackId": 63,
|
||||
"type": "plain",
|
||||
"lastModified": 1689667506,
|
||||
"lastEditor": null,
|
||||
"createdAt": 1689667502,
|
||||
"labels": [],
|
||||
"assignedUsers": [],
|
||||
"attachments": null,
|
||||
"attachmentCount": null,
|
||||
"owner": {
|
||||
"primaryKey": "admin",
|
||||
"uid": "admin",
|
||||
"displayname": "admin",
|
||||
"type": 0
|
||||
},
|
||||
"order": 1,
|
||||
"archived": false,
|
||||
"duedate": null,
|
||||
"deletedAt": 0,
|
||||
"commentsUnread": 0,
|
||||
"commentsCount": 0,
|
||||
"ETag": "193163d8a8acedbfaba196b1f0d65bc8",
|
||||
"overdue": 0,
|
||||
"boardId": 189,
|
||||
"board": {
|
||||
"id": 189,
|
||||
"title": "Shared board"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"activeSessions": [],
|
||||
"deletedAt": 0,
|
||||
"lastModified": 1689667537,
|
||||
"settings": [],
|
||||
"ETag": "6c315c83f146485e6b2b6fdc24ffa617"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,7 +277,7 @@ class TransferOwnershipTest extends \Test\TestCase {
|
||||
// Arrange separate board next to the one being transferred
|
||||
$board = $this->boardService->create('Test 2', self::TEST_USER_1, '000000');
|
||||
$id = $board->getId();
|
||||
$this->boardService->addAcl($id, Acl::PERMISSION_TYPE_USER, self::TEST_USER_1, true, true, true);
|
||||
// $this->boardService->addAcl($id, Acl::PERMISSION_TYPE_USER, self::TEST_USER_1, true, true, true);
|
||||
$this->boardService->addAcl($id, Acl::PERMISSION_TYPE_GROUP, self::TEST_GROUP, true, true, true);
|
||||
$this->boardService->addAcl($id, Acl::PERMISSION_TYPE_USER, self::TEST_USER_3, false, true, false);
|
||||
$stacks[] = $this->stackService->create('Stack A', $id, 1);
|
||||
|
||||
415
tests/integration/import/ImportExportTest.php
Normal file
415
tests/integration/import/ImportExportTest.php
Normal file
@@ -0,0 +1,415 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2017 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Db;
|
||||
|
||||
use OCA\Deck\Command\BoardImport;
|
||||
use OCA\Deck\Command\UserExport;
|
||||
use OCA\Deck\Service\BoardService;
|
||||
use OCA\Deck\Service\CardService;
|
||||
use OCA\Deck\Service\Importer\BoardImportService;
|
||||
use OCA\Deck\Service\Importer\Systems\DeckJsonService;
|
||||
use OCA\Deck\Service\PermissionService;
|
||||
use OCA\Deck\Service\StackService;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Server;
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\BufferedOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @group DB
|
||||
*/
|
||||
class ImportExportTest extends \Test\TestCase {
|
||||
private IDBConnection $connection;
|
||||
private const TEST_USER1 = 'test-export-user1';
|
||||
private const TEST_USER3 = 'test-export-user3';
|
||||
private const TEST_USER2 = 'test-export-user2';
|
||||
private const TEST_USER4 = 'test-export-user4';
|
||||
private const TEST_GROUP1 = 'test-export-group1';
|
||||
|
||||
public static function setUpBeforeClass(): void {
|
||||
parent::setUpBeforeClass();
|
||||
|
||||
$backend = new \Test\Util\User\Dummy();
|
||||
\OC_User::useBackend($backend);
|
||||
Server::get(IUserManager::class)->registerBackend($backend);
|
||||
$backend->createUser('alice', 'alice');
|
||||
$backend->createUser('jane', 'jane');
|
||||
$backend->createUser('johndoe', 'johndoe');
|
||||
$backend->createUser(self::TEST_USER1, self::TEST_USER1);
|
||||
$backend->createUser(self::TEST_USER2, self::TEST_USER2);
|
||||
$backend->createUser(self::TEST_USER3, self::TEST_USER3);
|
||||
$backend->createUser(self::TEST_USER4, self::TEST_USER4);
|
||||
// create group
|
||||
$groupBackend = new \Test\Util\Group\Dummy();
|
||||
$groupBackend->createGroup(self::TEST_GROUP1);
|
||||
$groupBackend->createGroup('group');
|
||||
$groupBackend->createGroup('group1');
|
||||
$groupBackend->createGroup('group2');
|
||||
$groupBackend->createGroup('group3');
|
||||
$groupBackend->addToGroup(self::TEST_USER1, 'group');
|
||||
$groupBackend->addToGroup(self::TEST_USER2, 'group');
|
||||
$groupBackend->addToGroup(self::TEST_USER3, 'group');
|
||||
$groupBackend->addToGroup(self::TEST_USER2, 'group1');
|
||||
$groupBackend->addToGroup(self::TEST_USER3, 'group2');
|
||||
$groupBackend->addToGroup(self::TEST_USER4, 'group3');
|
||||
$groupBackend->addToGroup(self::TEST_USER2, self::TEST_GROUP1);
|
||||
Server::get(IGroupManager::class)->addBackend($groupBackend);
|
||||
|
||||
Server::get(PermissionService::class)->setUserId('admin');
|
||||
}
|
||||
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->connection = \OCP\Server::get(IDBConnection::class);
|
||||
$this->cleanDb();
|
||||
$this->cleanDb(self::TEST_USER1);
|
||||
}
|
||||
|
||||
public function testImportOcc() {
|
||||
$this->importFromFile(__DIR__ . '/../../data/deck.json');
|
||||
$this->assertDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* This test runs an import, export and another import and
|
||||
* assert that multiple attempts result in the same data structure
|
||||
*
|
||||
* In addition, it asserts that multiple import/export runs result in the same JSON
|
||||
*/
|
||||
public function testReimportOcc() {
|
||||
$this->importFromFile(__DIR__ . '/../../data/deck.json');
|
||||
$this->assertDatabase();
|
||||
|
||||
$tmpExportFile = $this->exportToTemp();
|
||||
|
||||
// Useful for double checking differences as there is no easy way to compare equal with skipping certain id keys, etag
|
||||
// self::assertEquals(file_get_contents(__DIR__ . '/../../data/deck.json'), $jsonOutput);
|
||||
self::assertEquals(
|
||||
self::writeArrayStructure(array: json_decode(file_get_contents(__DIR__ . '/../../data/deck.json'), true)),
|
||||
self::writeArrayStructure(array: json_decode(file_get_contents($tmpExportFile), true))
|
||||
);
|
||||
|
||||
// cleanup test database
|
||||
$this->cleanDb();
|
||||
|
||||
// Re-import from temporary file
|
||||
$this->importFromFile($tmpExportFile);
|
||||
$this->assertDatabase();
|
||||
|
||||
$tmpExportFile2 = $this->exportToTemp();
|
||||
self::assertEquals(
|
||||
self::writeArrayStructure(array: json_decode(file_get_contents($tmpExportFile), true)),
|
||||
self::writeArrayStructure(array: json_decode(file_get_contents($tmpExportFile2), true))
|
||||
);
|
||||
}
|
||||
|
||||
public static function writeArrayStructure(string $prefix = '', array $array = [], array $skipKeyList = ['id', 'boardId', 'cardId', 'stackId', 'ETag', 'permissions', 'shared', 'version']): string {
|
||||
$output = '';
|
||||
$arrayIsList = array_keys($array) === range(0, count($array) - 1);
|
||||
foreach ($array as $key => $value) {
|
||||
$tmpPrefix = $prefix;
|
||||
if (in_array($key, $skipKeyList)) {
|
||||
continue;
|
||||
}
|
||||
if (is_array($value)) {
|
||||
if ($key === 'participant' || $key === 'owner') {
|
||||
$output .= $tmpPrefix . $key . ' => ' . $value['primaryKey'] . PHP_EOL;
|
||||
continue;
|
||||
}
|
||||
$tmpPrefix .= (!$arrayIsList && !is_numeric($key) ? $key : '!!!') . ' => ';
|
||||
$output .= self::writeArrayStructure($tmpPrefix, $value, $skipKeyList);
|
||||
} else {
|
||||
$output .= $tmpPrefix . $key . ' => ' . $value . PHP_EOL;
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function cleanDb(string $owner = 'admin'): void {
|
||||
$this->connection->executeQuery('DELETE from oc_deck_boards;');
|
||||
}
|
||||
|
||||
private function importFromFile(string $filePath): void {
|
||||
$input = $this->createMock(InputInterface::class);
|
||||
$input->expects($this->any())
|
||||
->method('getOption')
|
||||
->willReturnCallback(function ($arg) use ($filePath) {
|
||||
return match ($arg) {
|
||||
'system' => 'DeckJson',
|
||||
'data' => $filePath,
|
||||
'config' => __DIR__ . '/../../data/config-trelloJson.json',
|
||||
};
|
||||
});
|
||||
$output = $this->createMock(OutputInterface::class);
|
||||
$importer = self::getFreshService(BoardImport::class);
|
||||
$application = new Application();
|
||||
$importer->setApplication($application);
|
||||
$importer->run($input, $output);
|
||||
}
|
||||
|
||||
/** Returns the path of a deck export json */
|
||||
private function exportToTemp(): string {
|
||||
\OCP\Server::get(BoardMapper::class)->flushCache();
|
||||
$application = new Application();
|
||||
$input = $this->createMock(InputInterface::class);
|
||||
$input->expects($this->any())
|
||||
->method('getArgument')
|
||||
->with('user-id')
|
||||
->willReturn('admin');
|
||||
$output = new BufferedOutput();
|
||||
$exporter = new UserExport(
|
||||
\OCP\Server::get(IAppManager::class),
|
||||
self::getFreshService(BoardMapper::class),
|
||||
self::getFreshService(BoardService::class),
|
||||
self::getFreshService(StackMapper::class),
|
||||
self::getFreshService(CardMapper::class),
|
||||
self::getFreshService(AssignmentMapper::class),
|
||||
);
|
||||
$exporter->setApplication($application);
|
||||
$exporter->run($input, $output);
|
||||
$jsonOutput = $output->fetch();
|
||||
json_decode($jsonOutput);
|
||||
self::assertTrue(json_last_error() === JSON_ERROR_NONE);
|
||||
$tmpExportFile = tempnam('/tmp', 'export');
|
||||
file_put_contents($tmpExportFile, $jsonOutput);
|
||||
return $tmpExportFile;
|
||||
}
|
||||
|
||||
public function testImport() {
|
||||
$importer = self::getFreshService(BoardImportService::class);
|
||||
$deckJsonService = self::getFreshService(DeckJsonService::class);
|
||||
$deckJsonService->setImportService($importer);
|
||||
|
||||
$importer->setSystem('DeckJson');
|
||||
$importer->setImportSystem($deckJsonService);
|
||||
$importer->setConfigInstance(json_decode(file_get_contents(__DIR__ . '/../../data/config-trelloJson.json')));
|
||||
$importer->setData(json_decode(file_get_contents(__DIR__ . '/../../data/deck.json')));
|
||||
$importer->import();
|
||||
|
||||
$this->assertDatabase();
|
||||
}
|
||||
|
||||
public function testImportAsOtherUser() {
|
||||
$importer = self::getFreshService(BoardImportService::class);
|
||||
$deckJsonService = self::getFreshService(DeckJsonService::class);
|
||||
$deckJsonService->setImportService($importer);
|
||||
|
||||
$importer->setSystem('DeckJson');
|
||||
$importer->setImportSystem($deckJsonService);
|
||||
$importer->setConfigInstance((object)[
|
||||
'owner' => self::TEST_USER1
|
||||
]);
|
||||
$importer->setData(json_decode(file_get_contents(__DIR__ . '/../../data/deck.json')));
|
||||
$importer->import();
|
||||
|
||||
$this->assertDatabase(self::TEST_USER1);
|
||||
}
|
||||
|
||||
public function testImportWithRemap() {
|
||||
$importer = self::getFreshService(BoardImportService::class);
|
||||
$deckJsonService = self::getFreshService(DeckJsonService::class);
|
||||
$deckJsonService->setImportService($importer);
|
||||
|
||||
$importer->setSystem('DeckJson');
|
||||
$importer->setImportSystem($deckJsonService);
|
||||
$importer->setConfigInstance((object)[
|
||||
'owner' => self::TEST_USER1,
|
||||
'uidRelation' => (object)[
|
||||
'alice' => self::TEST_USER2,
|
||||
'jane' => self::TEST_USER3,
|
||||
],
|
||||
]);
|
||||
$importer->setData(json_decode(file_get_contents(__DIR__ . '/../../data/deck.json')));
|
||||
$importer->import();
|
||||
|
||||
$this->assertDatabase(self::TEST_USER1);
|
||||
$otherUserboards = self::getFreshService(BoardMapper::class)->findAllByUser(self::TEST_USER2);
|
||||
self::assertCount(1, $otherUserboards);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param class-string<T>|string $className
|
||||
* @return T
|
||||
*/
|
||||
private function getFreshService(string $className): mixed {
|
||||
$fresh = \OC::$server->getRegisteredAppContainer('deck')->resolve($className);
|
||||
self::overwriteService($className, $fresh);
|
||||
return $fresh;
|
||||
}
|
||||
|
||||
public function assertDatabase(string $owner = 'admin') {
|
||||
$permissionService = self::getFreshService(PermissionService::class);
|
||||
$permissionService->setUserId($owner);
|
||||
self::getFreshService(BoardService::class);
|
||||
self::getFreshService(CardService::class);
|
||||
$boardMapper = self::getFreshService(BoardMapper::class);
|
||||
$stackMapper = self::getFreshService(StackMapper::class);
|
||||
$cardMapper = self::getFreshService(CardMapper::class);
|
||||
|
||||
$boards = $boardMapper->findAllByOwner($owner);
|
||||
$boardNames = array_map(fn ($board) => $board->getTitle(), $boards);
|
||||
self::assertEquals(2, count($boards));
|
||||
|
||||
$board = $boards[0];
|
||||
self::assertEntity(Board::fromRow([
|
||||
'title' => 'My test board',
|
||||
'color' => 'e0ed31',
|
||||
'owner' => $owner,
|
||||
'lastModified' => 1689667796,
|
||||
]), $board);
|
||||
$boardService = $this->getFreshService(BoardService::class);
|
||||
$fullBoard = $boardService->find($board->getId(), true);
|
||||
self::assertEntityInArray(Label::fromParams([
|
||||
'title' => 'L2',
|
||||
'color' => '31CC7C',
|
||||
]), $fullBoard->getLabels(), true);
|
||||
|
||||
|
||||
$stacks = $stackMapper->findAll($board->getId());
|
||||
self::assertCount(3, $stacks);
|
||||
self::assertEntity(Stack::fromRow([
|
||||
'title' => 'A',
|
||||
'order' => 999,
|
||||
'boardId' => $boards[0]->getId(),
|
||||
'lastModified' => 1689667779,
|
||||
]), $stacks[0]);
|
||||
self::assertEntity(Stack::fromRow([
|
||||
'title' => 'B',
|
||||
'order' => 999,
|
||||
'boardId' => $boards[0]->getId(),
|
||||
'lastModified' => 1689667796,
|
||||
]), $stacks[1]);
|
||||
self::assertEntity(Stack::fromRow([
|
||||
'title' => 'C',
|
||||
'order' => 999,
|
||||
'boardId' => $boards[0]->getId(),
|
||||
'lastModified' => 0,
|
||||
]), $stacks[2]);
|
||||
|
||||
$cards = $cardMapper->findAll($stacks[0]->getId());
|
||||
self::assertEntity(Card::fromRow([
|
||||
'title' => '1',
|
||||
'description' => '',
|
||||
'type' => 'plain',
|
||||
'lastModified' => 1689667779,
|
||||
'createdAt' => 1689667569,
|
||||
'owner' => $owner,
|
||||
'duedate' => new \DateTime('2050-07-24T22:00:00.000000+0000'),
|
||||
'order' => 999,
|
||||
'stackId' => $stacks[0]->getId(),
|
||||
]), $cards[0]);
|
||||
self::assertEntity(Card::fromRow([
|
||||
'title' => '2',
|
||||
'duedate' => new \DateTime('2050-07-24T22:00:00.000000+0000'),
|
||||
]), $cards[1], true);
|
||||
self::assertEntity(Card::fromParams([
|
||||
'title' => '3',
|
||||
'duedate' => null,
|
||||
]), $cards[2], true);
|
||||
|
||||
$cards = $cardMapper->findAll($stacks[1]->getId());
|
||||
self::assertEntity(Card::fromParams([
|
||||
'title' => '6',
|
||||
'duedate' => null,
|
||||
'description' => "# Test description\n\nHello world",
|
||||
]), $cards[2], true);
|
||||
|
||||
// Shared board
|
||||
$sharedBoard = $boards[1];
|
||||
self::assertEntity(Board::fromRow([
|
||||
'title' => 'Shared board',
|
||||
'color' => '30b6d8',
|
||||
'owner' => $owner,
|
||||
]), $sharedBoard, true);
|
||||
|
||||
$stackService = self::getFreshService(StackService::class);
|
||||
$stacks = $stackService->findAll($board->getId());
|
||||
self::assertEntityInArray(Label::fromParams([
|
||||
'title' => 'L2',
|
||||
'color' => '31CC7C',
|
||||
]), $stacks[0]->getCards()[0]->getLabels(), true);
|
||||
self::assertEntity(Label::fromParams([
|
||||
'title' => 'L2',
|
||||
'color' => '31CC7C',
|
||||
]), $stacks[0]->getCards()[0]->getLabels()[0], true);
|
||||
|
||||
$stacks = $stackMapper->findAll($sharedBoard->getId());
|
||||
self::assertCount(3, $stacks);
|
||||
}
|
||||
|
||||
public static function assertEntityInArray(Entity $expected, array $array, bool $checkProperties): void {
|
||||
$exists = null;
|
||||
foreach ($array as $entity) {
|
||||
try {
|
||||
self::assertEntity($expected, $entity, $checkProperties);
|
||||
$exists = $entity;
|
||||
} catch (ExpectationFailedException $e) {
|
||||
}
|
||||
}
|
||||
if ($exists) {
|
||||
self::assertEntity($expected, $exists, $checkProperties);
|
||||
} else {
|
||||
// THis is hard to debug if it fails as the actual diff is not returned but hidden in the above exception
|
||||
self::assertEquals($expected, $exists);
|
||||
}
|
||||
}
|
||||
|
||||
public static function assertEntity(Entity $expected, Entity $actual, bool $checkProperties = false): void {
|
||||
if ($checkProperties === true) {
|
||||
$e = clone $expected;
|
||||
$a = clone $actual;
|
||||
foreach ($e->getUpdatedFields() as $property => $updated) {
|
||||
$expectedValue = call_user_func([$e, 'get' . ucfirst($property)]);
|
||||
$actualValue = call_user_func([$a, 'get' . ucfirst($property)]);
|
||||
self::assertEquals(
|
||||
$expectedValue,
|
||||
$actualValue
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$e = clone $expected;
|
||||
$e->setId(null);
|
||||
$a = clone $actual;
|
||||
$a->setId(null);
|
||||
$e->resetUpdatedFields();
|
||||
$a->resetUpdatedFields();
|
||||
self::assertEquals($e, $a);
|
||||
}
|
||||
}
|
||||
|
||||
public function tearDown(): void {
|
||||
$this->cleanDb();
|
||||
$this->cleanDb(self::TEST_USER1);
|
||||
parent::tearDown();
|
||||
}
|
||||
}
|
||||
@@ -12,5 +12,8 @@
|
||||
<testsuite name="integration-app">
|
||||
<directory>./integration/app</directory>
|
||||
</testsuite>
|
||||
<testsuite name="integration-import">
|
||||
<directory>./integration/import</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
||||
|
||||
@@ -193,6 +193,8 @@ namespace Symfony\Component\Console\Output {
|
||||
class OutputInterface {
|
||||
public const VERBOSITY_VERBOSE = 1;
|
||||
public function writeln($text, int $flat = 0) {}
|
||||
public function isVerbose(): bool {}
|
||||
public function isVeryVerbose(): bool {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,12 +31,14 @@ use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Db\Stack;
|
||||
use OCA\Deck\Db\StackMapper;
|
||||
use OCA\Deck\Service\BoardService;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IUserManager;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class UserExportTest extends \Test\TestCase {
|
||||
protected $appManager;
|
||||
protected $boardMapper;
|
||||
protected $boardService;
|
||||
protected $stackMapper;
|
||||
@@ -45,10 +47,11 @@ class UserExportTest extends \Test\TestCase {
|
||||
protected $userManager;
|
||||
protected $groupManager;
|
||||
|
||||
private $userExport;
|
||||
private UserExport $userExport;
|
||||
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->appManager = $this->createMock(IAppManager::class);
|
||||
$this->boardMapper = $this->createMock(BoardMapper::class);
|
||||
$this->boardService = $this->createMock(BoardService::class);
|
||||
$this->stackMapper = $this->createMock(StackMapper::class);
|
||||
@@ -56,7 +59,7 @@ class UserExportTest extends \Test\TestCase {
|
||||
$this->assignedUserMapper = $this->createMock(AssignmentMapper::class);
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->groupManager = $this->createMock(IGroupManager::class);
|
||||
$this->userExport = new UserExport($this->boardMapper, $this->boardService, $this->stackMapper, $this->cardMapper, $this->assignedUserMapper, $this->userManager, $this->groupManager);
|
||||
$this->userExport = new UserExport($this->appManager, $this->boardMapper, $this->boardService, $this->stackMapper, $this->cardMapper, $this->assignedUserMapper, $this->userManager, $this->groupManager);
|
||||
}
|
||||
|
||||
public function getBoard($id) {
|
||||
@@ -114,5 +117,6 @@ class UserExportTest extends \Test\TestCase {
|
||||
->method('findAll')
|
||||
->willReturn([]);
|
||||
$result = $this->invokePrivate($this->userExport, 'execute', [$input, $output]);
|
||||
self::assertEquals(0, $result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ use OCP\IDBConnection;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class BoardImportServiceTest extends \Test\TestCase {
|
||||
/** @var IDBConnection|MockObject */
|
||||
@@ -92,7 +93,8 @@ class BoardImportServiceTest extends \Test\TestCase {
|
||||
$this->attachmentMapper,
|
||||
$this->cardMapper,
|
||||
$this->commentsManager,
|
||||
$this->eventDispatcher
|
||||
$this->eventDispatcher,
|
||||
$this->createMock(LoggerInterface::class),
|
||||
);
|
||||
|
||||
$this->boardImportService->setSystem('trelloJson');
|
||||
@@ -118,6 +120,9 @@ class BoardImportServiceTest extends \Test\TestCase {
|
||||
$this->trelloJsonService
|
||||
->method('getJsonSchemaPath')
|
||||
->willReturn($configFile);
|
||||
$this->trelloJsonService
|
||||
->method('getBoards')
|
||||
->willReturn([$data]);
|
||||
$this->boardImportService->setImportSystem($this->trelloJsonService);
|
||||
|
||||
$owner = $this->createMock(IUser::class);
|
||||
@@ -142,6 +147,9 @@ class BoardImportServiceTest extends \Test\TestCase {
|
||||
}
|
||||
|
||||
public function testImportSuccess() {
|
||||
$this->userManager->method('userExists')
|
||||
->willReturn(true);
|
||||
|
||||
$this->boardMapper
|
||||
->expects($this->once())
|
||||
->method('insert');
|
||||
@@ -192,8 +200,7 @@ class BoardImportServiceTest extends \Test\TestCase {
|
||||
->expects($this->once())
|
||||
->method('insert');
|
||||
|
||||
$actual = $this->boardImportService->import();
|
||||
|
||||
$this->assertNull($actual);
|
||||
$this->boardImportService->import();
|
||||
self::assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
||||
86
tests/unit/Service/Importer/Systems/DeckJsonServiceTest.php
Normal file
86
tests/unit/Service/Importer/Systems/DeckJsonServiceTest.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OCA\Deck\Service\Importer\Systems;
|
||||
|
||||
use OCA\Deck\Service\Importer\BoardImportService;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Server;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
|
||||
/**
|
||||
* @group DB
|
||||
*/
|
||||
class DeckJsonServiceTest extends \Test\TestCase {
|
||||
private DeckJsonService $service;
|
||||
/** @var IURLGenerator|MockObject */
|
||||
private $urlGenerator;
|
||||
/** @var IUserManager|MockObject */
|
||||
private $userManager;
|
||||
/** @var IL10N */
|
||||
private $l10n;
|
||||
public function setUp(): void {
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->urlGenerator = $this->createMock(IURLGenerator::class);
|
||||
$this->l10n = $this->createMock(IL10N::class);
|
||||
$this->service = new DeckJsonService(
|
||||
$this->userManager,
|
||||
$this->urlGenerator,
|
||||
$this->l10n
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetBoardWithNoName() {
|
||||
$this->expectExceptionMessage('Invalid name of board');
|
||||
$importService = $this->createMock(BoardImportService::class);
|
||||
$this->service->setImportService($importService);
|
||||
$this->service->getBoard();
|
||||
}
|
||||
|
||||
public function testGetBoardWithSuccess() {
|
||||
$importService = Server::get(BoardImportService::class);
|
||||
|
||||
$data = json_decode(file_get_contents(__DIR__ . '/../../../../data/deck.json'));
|
||||
$importService->setData($data);
|
||||
|
||||
$configInstance = json_decode(file_get_contents(__DIR__ . '/../../../../data/config-deckJson.json'));
|
||||
$importService->setConfigInstance($configInstance);
|
||||
|
||||
$owner = $this->createMock(IUser::class);
|
||||
$owner
|
||||
->method('getUID')
|
||||
->willReturn('admin');
|
||||
$importService->setConfig('owner', $owner);
|
||||
|
||||
$this->service->setImportService($importService);
|
||||
|
||||
$boards = $this->service->getBoards();
|
||||
$importService->setData($boards[0]);
|
||||
$actual = $this->service->getBoard();
|
||||
$this->assertEquals('My test board', $actual->getTitle());
|
||||
$this->assertEquals('admin', $actual->getOwner());
|
||||
$this->assertEquals('e0ed31', $actual->getColor());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user