mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-08 08:12:17 +01:00
Merge branch 'feature/import' into feature/add_frontend_dashboard
# Conflicts: # app/Http/Controllers/Api/V1/TimeEntryController.php # app/Providers/JetstreamServiceProvider.php
This commit is contained in:
88
tests/Unit/Endpoint/Api/V1/ImportEndpointTest.php
Normal file
88
tests/Unit/Endpoint/Api/V1/ImportEndpointTest.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Endpoint\Api\V1;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Service\Import\Importers\ReportDto;
|
||||
use App\Service\Import\ImportService;
|
||||
use Laravel\Passport\Passport;
|
||||
use Mockery\MockInterface;
|
||||
|
||||
class ImportEndpointTest extends ApiEndpointTestAbstract
|
||||
{
|
||||
public function test_import_fails_if_user_does_not_have_permission()
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission([
|
||||
]);
|
||||
|
||||
Passport::actingAs($data->user);
|
||||
|
||||
// Act
|
||||
$response = $this->postJson(route('api.v1.import.import', ['organization' => $data->organization->id]), [
|
||||
'type' => 'toggl_time_entries',
|
||||
'data' => 'some data',
|
||||
'options' => [],
|
||||
]);
|
||||
|
||||
// Assert
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
public function test_import_calls_import_service_if_user_has_permission(): void
|
||||
{
|
||||
// Arrange
|
||||
$user = $this->createUserWithPermission([
|
||||
'import',
|
||||
]);
|
||||
$this->mock(ImportService::class, function (MockInterface $mock) use (&$user): void {
|
||||
$mock->shouldReceive('import')
|
||||
->withArgs(function (Organization $organization, string $importerType, string $data) use (&$user): bool {
|
||||
return $organization->is($user->organization) && $importerType === 'toggl_time_entries' && $data === 'some data';
|
||||
})
|
||||
->andReturn(new ReportDto(
|
||||
clientsCreated: 1,
|
||||
projectsCreated: 2,
|
||||
tasksCreated: 3,
|
||||
timeEntriesCreated: 4,
|
||||
tagsCreated: 5,
|
||||
usersCreated: 6,
|
||||
))
|
||||
->once();
|
||||
});
|
||||
Passport::actingAs($user->user);
|
||||
|
||||
// Act
|
||||
$response = $this->postJson(route('api.v1.import.import', ['organization' => $user->organization->id]), [
|
||||
'type' => 'toggl_time_entries',
|
||||
'data' => 'some data',
|
||||
]);
|
||||
|
||||
// Assert
|
||||
$response->assertStatus(200);
|
||||
$response->assertExactJson([
|
||||
'report' => [
|
||||
'clients' => [
|
||||
'created' => 1,
|
||||
],
|
||||
'projects' => [
|
||||
'created' => 2,
|
||||
],
|
||||
'tasks' => [
|
||||
'created' => 3,
|
||||
],
|
||||
'time-entries' => [
|
||||
'created' => 4,
|
||||
],
|
||||
'tags' => [
|
||||
'created' => 5,
|
||||
],
|
||||
'users' => [
|
||||
'created' => 6,
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
85
tests/Unit/Endpoint/Api/V1/UserEndpointTest.php
Normal file
85
tests/Unit/Endpoint/Api/V1/UserEndpointTest.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Endpoint\Api\V1;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Models\User;
|
||||
use Laravel\Passport\Passport;
|
||||
|
||||
class UserEndpointTest extends ApiEndpointTestAbstract
|
||||
{
|
||||
public function test_index_returns_members_of_organization(): void
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission([
|
||||
'users:view',
|
||||
]);
|
||||
Passport::actingAs($data->user);
|
||||
|
||||
// Act
|
||||
$response = $this->getJson(route('api.v1.users.index', $data->organization->id));
|
||||
|
||||
// Assert
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
public function test_invite_placeholder_fails_if_user_does_not_have_permission(): void
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission([
|
||||
]);
|
||||
$user = User::factory()->create([
|
||||
'is_placeholder' => true,
|
||||
]);
|
||||
$data->organization->users()->attach($user);
|
||||
Passport::actingAs($data->user);
|
||||
|
||||
// Act
|
||||
$response = $this->postJson(route('api.v1.users.invite-placeholder', ['organization' => $data->organization->id, 'user' => $user->id]));
|
||||
|
||||
// Assert
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
public function test_invite_placeholder_fails_if_user_is_not_part_of_organization(): void
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission([
|
||||
'users:invite-placeholder',
|
||||
]);
|
||||
$otherOrganization = Organization::factory()->create();
|
||||
$user = User::factory()->create([
|
||||
'is_placeholder' => true,
|
||||
]);
|
||||
$otherOrganization->users()->attach($user);
|
||||
Passport::actingAs($data->user);
|
||||
|
||||
// Act
|
||||
$response = $this->postJson(route('api.v1.users.invite-placeholder', ['organization' => $data->organization->id, 'user' => $user->id]));
|
||||
|
||||
// Assert
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
public function test_invite_placeholder_returns_400_if_user_is_not_placeholder(): void
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission([
|
||||
'users:invite-placeholder',
|
||||
]);
|
||||
Passport::actingAs($data->user);
|
||||
|
||||
// Act
|
||||
$response = $this->postJson(route('api.v1.users.invite-placeholder', ['organization' => $data->organization->id, 'user' => $data->user->id]));
|
||||
|
||||
// Assert
|
||||
$response->assertStatus(400);
|
||||
$response->assertExactJson([
|
||||
'error' => true,
|
||||
'key' => 'user_not_placeholder',
|
||||
'message' => 'The given user is not a placeholder',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Model;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Models\TimeEntry;
|
||||
use App\Models\User;
|
||||
use App\Providers\Filament\AdminPanelProvider;
|
||||
use Filament\Panel;
|
||||
@@ -42,4 +44,47 @@ class UserModelTest extends ModelTestAbstract
|
||||
// Assert
|
||||
$this->assertTrue($canAccess);
|
||||
}
|
||||
|
||||
public function test_scope_belongs_to_organization_returns_only_users_of_organization_including_owners(): void
|
||||
{
|
||||
// Arrange
|
||||
$owner = User::factory()->create();
|
||||
$organization = Organization::factory()->withOwner($owner)->create();
|
||||
$user = User::factory()->create();
|
||||
$user->organizations()->attach($organization, [
|
||||
'role' => 'employee',
|
||||
]);
|
||||
$otherOrganization = Organization::factory()->create();
|
||||
$otherUser = User::factory()->create();
|
||||
$otherUser->organizations()->attach($otherOrganization, [
|
||||
'role' => 'employee',
|
||||
]);
|
||||
|
||||
// Act
|
||||
$users = User::query()
|
||||
->belongsToOrganization($organization)
|
||||
->get();
|
||||
|
||||
// Assert
|
||||
$this->assertCount(2, $users);
|
||||
$userIds = $users->pluck('id')->toArray();
|
||||
$this->assertContains($user->getKey(), $userIds);
|
||||
$this->assertContains($owner->getKey(), $userIds);
|
||||
}
|
||||
|
||||
public function test_it_has_many_time_entries(): void
|
||||
{
|
||||
// Arrange
|
||||
$user = User::factory()->create();
|
||||
$timeEntries = TimeEntry::factory()->forUser($user)->createMany(3);
|
||||
|
||||
// Act
|
||||
$user->refresh();
|
||||
$timeEntriesRel = $user->timeEntries;
|
||||
|
||||
// Assert
|
||||
$this->assertNotNull($timeEntriesRel);
|
||||
$this->assertCount(3, $timeEntriesRel);
|
||||
$this->assertTrue($timeEntriesRel->first()->is($timeEntries->first()));
|
||||
}
|
||||
}
|
||||
|
||||
66
tests/Unit/Rules/ColorRuleTest.php
Normal file
66
tests/Unit/Rules/ColorRuleTest.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Rules;
|
||||
|
||||
use App\Rules\ColorRule;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ColorRuleTest extends TestCase
|
||||
{
|
||||
public function test_validation_passes_if_value_is_valid_color(): void
|
||||
{
|
||||
// Arrange
|
||||
$validator = Validator::make([
|
||||
'color' => '#ef5350',
|
||||
], [
|
||||
'color' => [new ColorRule()],
|
||||
]);
|
||||
|
||||
// Act
|
||||
$isValid = $validator->passes();
|
||||
$messages = $validator->messages()->toArray();
|
||||
|
||||
// Assert
|
||||
$this->assertTrue($isValid);
|
||||
$this->assertArrayNotHasKey('color', $messages);
|
||||
}
|
||||
|
||||
public function test_validation_fails_if_value_is_not_a_string(): void
|
||||
{
|
||||
// Arrange
|
||||
$validator = Validator::make([
|
||||
'color' => true,
|
||||
], [
|
||||
'color' => [new ColorRule()],
|
||||
]);
|
||||
|
||||
// Act
|
||||
$isValid = $validator->passes();
|
||||
$messages = $validator->messages()->toArray();
|
||||
|
||||
// Assert
|
||||
$this->assertFalse($isValid);
|
||||
$this->assertEquals('The color field must be a string.', $messages['color'][0]);
|
||||
}
|
||||
|
||||
public function test_validation_fails_if_value_is_not_a_valid_color(): void
|
||||
{
|
||||
// Arrange
|
||||
$validator = Validator::make([
|
||||
'color' => 'rgb(0,0,0)',
|
||||
], [
|
||||
'color' => [new ColorRule()],
|
||||
]);
|
||||
|
||||
// Act
|
||||
$isValid = $validator->passes();
|
||||
$messages = $validator->messages()->toArray();
|
||||
|
||||
// Assert
|
||||
$this->assertFalse($isValid);
|
||||
$this->assertEquals('The color field must be a valid color.', $messages['color'][0]);
|
||||
}
|
||||
}
|
||||
138
tests/Unit/Service/Import/ImportDatabaseHelperTest.php
Normal file
138
tests/Unit/Service/Import/ImportDatabaseHelperTest.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Service\Import;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Models\Project;
|
||||
use App\Models\User;
|
||||
use App\Service\Import\ImportDatabaseHelper;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ImportDatabaseHelperTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_get_key_attach_to_existing_returns_key_for_identifier_without_creating_model(): void
|
||||
{
|
||||
// Arrange
|
||||
$user = User::factory()->create();
|
||||
$helper = new ImportDatabaseHelper(User::class, ['email'], true);
|
||||
|
||||
// Act
|
||||
$key = $helper->getKey([
|
||||
'email' => $user->email,
|
||||
], [
|
||||
'name' => 'Test',
|
||||
]);
|
||||
|
||||
// Assert
|
||||
$this->assertSame($user->getKey(), $key);
|
||||
}
|
||||
|
||||
public function test_get_key_attach_to_existing_creates_model_if_not_existing(): void
|
||||
{
|
||||
// Arrange
|
||||
$helper = new ImportDatabaseHelper(User::class, ['email'], true);
|
||||
|
||||
// Act
|
||||
$key = $helper->getKey([
|
||||
'email' => 'test@mail.test',
|
||||
], [
|
||||
'name' => 'Test',
|
||||
]);
|
||||
|
||||
// Assert
|
||||
$this->assertNotNull($key);
|
||||
$this->assertDatabaseHas(User::class, [
|
||||
'email' => 'test@mail.test',
|
||||
'name' => 'Test',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_get_key_not_attach_to_existing_is_not_implemented_yet(): void
|
||||
{
|
||||
// Arrange
|
||||
$project = Project::factory()->create();
|
||||
$helper = new ImportDatabaseHelper(Project::class, ['name', 'organization_id'], false);
|
||||
|
||||
// Act
|
||||
try {
|
||||
$key = $helper->getKey([
|
||||
'name' => $project->name,
|
||||
'organization_id' => $project->organization_id,
|
||||
], [
|
||||
'color' => '#000000',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$this->assertSame('Not implemented', $e->getMessage());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Assert
|
||||
$this->fail();
|
||||
}
|
||||
|
||||
public function test_get_key_by_external_identifier_returns_key_for_external_identifier(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create();
|
||||
$project = Project::factory()->forOrganization($organization)->create();
|
||||
$externalIdentifier1 = '12345';
|
||||
$externalIdentifier2 = '54321';
|
||||
$helper = new ImportDatabaseHelper(Project::class, ['name', 'organization_id'], true);
|
||||
$helper->getKey([
|
||||
'name' => $project->name,
|
||||
'organization_id' => $organization->getKey(),
|
||||
], [
|
||||
'color' => '#000000',
|
||||
], $externalIdentifier1);
|
||||
$helper->getKey([
|
||||
'name' => 'Not existing project',
|
||||
'organization_id' => $organization->getKey(),
|
||||
], [
|
||||
'color' => '#000000',
|
||||
], $externalIdentifier2);
|
||||
|
||||
// Act
|
||||
$key1 = $helper->getKeyByExternalIdentifier($externalIdentifier1);
|
||||
$key2 = $helper->getKeyByExternalIdentifier($externalIdentifier2);
|
||||
|
||||
// Assert
|
||||
$this->assertSame($project->getKey(), $key1);
|
||||
$this->assertSame(Project::where('name', '=', 'Not existing project')->first()->getKey(), $key2);
|
||||
}
|
||||
|
||||
public function test_get_external_ids_returns_all_external_ids_that_were_temporary_stored_via_get_key(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create();
|
||||
$project = Project::factory()->forOrganization($organization)->create();
|
||||
$externalIdentifier1 = '12345';
|
||||
$externalIdentifier2 = '54321';
|
||||
$helper = new ImportDatabaseHelper(Project::class, ['name', 'organization_id'], true);
|
||||
$helper->getKey([
|
||||
'name' => $project->name,
|
||||
'organization_id' => $organization->getKey(),
|
||||
], [
|
||||
'color' => '#000000',
|
||||
], $externalIdentifier1);
|
||||
$helper->getKey([
|
||||
'name' => 'Not existing project',
|
||||
'organization_id' => $organization->getKey(),
|
||||
], [
|
||||
'color' => '#000000',
|
||||
], $externalIdentifier2);
|
||||
|
||||
// Act
|
||||
$externalKeys = $helper->getExternalIds();
|
||||
|
||||
// Assert
|
||||
$this->assertCount(2, $externalKeys);
|
||||
$this->assertContains($externalIdentifier1, $externalKeys);
|
||||
$this->assertContains($externalIdentifier2, $externalKeys);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Service\Import\Importer;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Service\Import\Importers\ClockifyProjectsImporter;
|
||||
|
||||
class ClockifyProjectsImporterTest extends ImporterTestAbstract
|
||||
{
|
||||
public function test_import_of_test_file_succeeds(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create();
|
||||
$importer = new ClockifyProjectsImporter();
|
||||
$importer->init($organization);
|
||||
$data = file_get_contents(storage_path('tests/clockify_projects_import_test_1.csv'));
|
||||
|
||||
// Act
|
||||
$importer->importData($data, []);
|
||||
|
||||
// Assert
|
||||
$this->checkTestScenarioProjectsOnlyAfterImport();
|
||||
}
|
||||
|
||||
public function test_import_of_test_file_twice_succeeds(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create();
|
||||
$importer = new ClockifyProjectsImporter();
|
||||
$importer->init($organization);
|
||||
$data = file_get_contents(storage_path('tests/clockify_projects_import_test_1.csv'));
|
||||
$importer->importData($data, []);
|
||||
$importer = new ClockifyProjectsImporter();
|
||||
$importer->init($organization);
|
||||
|
||||
// Act
|
||||
$importer->importData($data, []);
|
||||
|
||||
// Assert
|
||||
$this->checkTestScenarioProjectsOnlyAfterImport();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Service\Import\Importer;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Models\TimeEntry;
|
||||
use App\Service\Import\Importers\ClockifyTimeEntriesImporter;
|
||||
|
||||
class ClockifyTimeEntriesImporterTest extends ImporterTestAbstract
|
||||
{
|
||||
public function test_import_of_test_file_succeeds(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create();
|
||||
$importer = new ClockifyTimeEntriesImporter();
|
||||
$importer->init($organization);
|
||||
$data = file_get_contents(storage_path('tests/clockify_time_entries_import_test_1.csv'));
|
||||
|
||||
// Act
|
||||
$importer->importData($data, []);
|
||||
|
||||
// Assert
|
||||
$testScenario = $this->checkTestScenarioAfterImportExcludingTimeEntries();
|
||||
$timeEntries = TimeEntry::all();
|
||||
$this->assertCount(2, $timeEntries);
|
||||
$timeEntry1 = $timeEntries->firstWhere('description', '');
|
||||
$this->assertNotNull($timeEntry1);
|
||||
$this->assertSame('', $timeEntry1->description);
|
||||
$this->assertSame('2024-03-04 10:23:52', $timeEntry1->start->toDateTimeString());
|
||||
$this->assertSame('2024-03-04 10:23:52', $timeEntry1->end->toDateTimeString());
|
||||
$this->assertFalse($timeEntry1->billable);
|
||||
$this->assertSame([$testScenario->tag1->getKey(), $testScenario->tag2->getKey()], $timeEntry1->tags);
|
||||
$timeEntry2 = $timeEntries->firstWhere('description', 'Working hard');
|
||||
$this->assertNotNull($timeEntry2);
|
||||
$this->assertSame('Working hard', $timeEntry2->description);
|
||||
$this->assertSame('2024-03-04 10:23:00', $timeEntry2->start->toDateTimeString());
|
||||
$this->assertSame('2024-03-04 11:23:01', $timeEntry2->end->toDateTimeString());
|
||||
$this->assertTrue($timeEntry2->billable);
|
||||
$this->assertSame([], $timeEntry2->tags);
|
||||
}
|
||||
|
||||
public function test_import_of_test_file_twice_succeeds(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create();
|
||||
$importer = new ClockifyTimeEntriesImporter();
|
||||
$importer->init($organization);
|
||||
$data = file_get_contents(storage_path('tests/clockify_time_entries_import_test_1.csv'));
|
||||
$importer->importData($data, []);
|
||||
$importer = new ClockifyTimeEntriesImporter();
|
||||
$importer->init($organization);
|
||||
|
||||
// Act
|
||||
$importer->importData($data, []);
|
||||
|
||||
// Assert
|
||||
$testScenario = $this->checkTestScenarioAfterImportExcludingTimeEntries();
|
||||
$timeEntries = TimeEntry::all();
|
||||
$this->assertCount(4, $timeEntries);
|
||||
$timeEntry1 = $timeEntries->firstWhere('description', '');
|
||||
$this->assertNotNull($timeEntry1);
|
||||
$this->assertSame('', $timeEntry1->description);
|
||||
$this->assertSame('2024-03-04 10:23:52', $timeEntry1->start->toDateTimeString());
|
||||
$this->assertSame('2024-03-04 10:23:52', $timeEntry1->end->toDateTimeString());
|
||||
$this->assertFalse($timeEntry1->billable);
|
||||
$this->assertSame([$testScenario->tag1->getKey(), $testScenario->tag2->getKey()], $timeEntry1->tags);
|
||||
$timeEntry2 = $timeEntries->firstWhere('description', 'Working hard');
|
||||
$this->assertNotNull($timeEntry2);
|
||||
$this->assertSame('Working hard', $timeEntry2->description);
|
||||
$this->assertSame('2024-03-04 10:23:00', $timeEntry2->start->toDateTimeString());
|
||||
$this->assertSame('2024-03-04 11:23:01', $timeEntry2->end->toDateTimeString());
|
||||
$this->assertTrue($timeEntry2->billable);
|
||||
$this->assertSame([], $timeEntry2->tags);
|
||||
}
|
||||
}
|
||||
99
tests/Unit/Service/Import/Importer/ImporterTestAbstract.php
Normal file
99
tests/Unit/Service/Import/Importer/ImporterTestAbstract.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Service\Import\Importer;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\Project;
|
||||
use App\Models\Tag;
|
||||
use App\Models\Task;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ImporterTestAbstract extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
/**
|
||||
* @return object{user1: User, project1: Project, project2: Project, tag1: Tag, tag2: Tag}
|
||||
*/
|
||||
protected function checkTestScenarioAfterImportExcludingTimeEntries(): object
|
||||
{
|
||||
$users = User::all();
|
||||
$this->assertCount(2, $users);
|
||||
$user1 = $users->firstWhere('name', 'Peter Tester');
|
||||
$this->assertNotNull($user1);
|
||||
$this->assertSame(null, $user1->password);
|
||||
$this->assertSame('Peter Tester', $user1->name);
|
||||
$this->assertSame('peter.test@email.test', $user1->email);
|
||||
$clients = Client::all();
|
||||
$this->assertCount(1, $clients);
|
||||
$client1 = $clients->firstWhere('name', 'Big Company');
|
||||
$this->assertNotNull($client1);
|
||||
$projects = Project::all();
|
||||
$this->assertCount(2, $projects);
|
||||
$project1 = $projects->firstWhere('name', 'Project without Client');
|
||||
$this->assertNotNull($project1);
|
||||
$this->assertNull($project1->client_id);
|
||||
$project2 = $projects->firstWhere('name', 'Project for Big Company');
|
||||
$this->assertNotNull($project2);
|
||||
$this->assertSame($client1->getKey(), $project2->client_id);
|
||||
$tasks = Task::all();
|
||||
$this->assertCount(1, $tasks);
|
||||
$task1 = $tasks->firstWhere('name', 'Task 1');
|
||||
$this->assertNotNull($task1);
|
||||
$this->assertSame($project2->getKey(), $task1->project_id);
|
||||
$tags = Tag::all();
|
||||
$this->assertCount(2, $tags);
|
||||
$tag1 = $tags->firstWhere('name', 'Development');
|
||||
$tag2 = $tags->firstWhere('name', 'Backend');
|
||||
$this->assertNotNull($tag1);
|
||||
|
||||
return (object) [
|
||||
'user1' => $user1,
|
||||
'project1' => $project1,
|
||||
'project2' => $project2,
|
||||
'tag1' => $tag1,
|
||||
'tag2' => $tag2,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object{client1: Client, project1: Project, project2: Project, task1: Task}
|
||||
*/
|
||||
protected function checkTestScenarioProjectsOnlyAfterImport(): object
|
||||
{
|
||||
$clients = Client::all();
|
||||
$this->assertCount(1, $clients);
|
||||
$client1 = $clients->firstWhere('name', 'Big Company');
|
||||
$this->assertNotNull($client1);
|
||||
$projects = Project::all();
|
||||
$this->assertCount(2, $projects);
|
||||
$project1 = $projects->firstWhere('name', 'Project without Client');
|
||||
$this->assertNotNull($project1);
|
||||
$this->assertNull($project1->client_id);
|
||||
$project2 = $projects->firstWhere('name', 'Project for Big Company');
|
||||
$this->assertNotNull($project2);
|
||||
$this->assertSame($client1->getKey(), $project2->client_id);
|
||||
$tasks = Task::all();
|
||||
$this->assertCount(3, $tasks);
|
||||
$task1 = $tasks->firstWhere('name', 'Task 1');
|
||||
$this->assertNotNull($task1);
|
||||
$this->assertSame($project2->getKey(), $task1->project_id);
|
||||
$task2 = $tasks->firstWhere('name', 'Task 2');
|
||||
$this->assertNotNull($task2);
|
||||
$this->assertSame($project2->getKey(), $task2->project_id);
|
||||
$task3 = $tasks->firstWhere('name', 'Task 3');
|
||||
$this->assertNotNull($task3);
|
||||
$this->assertSame($project2->getKey(), $task3->project_id);
|
||||
|
||||
return (object) [
|
||||
'client1' => $client1,
|
||||
'project1' => $project1,
|
||||
'project2' => $project2,
|
||||
'task1' => $task1,
|
||||
];
|
||||
}
|
||||
}
|
||||
64
tests/Unit/Service/Import/Importer/TogglDataImporterTest.php
Normal file
64
tests/Unit/Service/Import/Importer/TogglDataImporterTest.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Service\Import\Importer;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Service\Import\Importers\TogglDataImporter;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\TemporaryDirectory\TemporaryDirectory;
|
||||
use ZipArchive;
|
||||
|
||||
class TogglDataImporterTest extends ImporterTestAbstract
|
||||
{
|
||||
private function createTestZip(string $folder): string
|
||||
{
|
||||
$tempDir = TemporaryDirectory::make();
|
||||
$zipPath = $tempDir->path('test.zip');
|
||||
$zip = new ZipArchive();
|
||||
$zip->open($zipPath, ZipArchive::CREATE);
|
||||
foreach (Storage::disk('testfiles')->allFiles($folder) as $file) {
|
||||
$zip->addFile(Storage::disk('testfiles')->path($file), Str::of($file)->after($folder.'/')->value());
|
||||
}
|
||||
$zip->close();
|
||||
|
||||
return $zipPath;
|
||||
}
|
||||
|
||||
public function test_import_of_test_file_succeeds(): void
|
||||
{
|
||||
// Arrange
|
||||
$zipPath = $this->createTestZip('toggl_data_import_test_1');
|
||||
$organization = Organization::factory()->create();
|
||||
$importer = new TogglDataImporter();
|
||||
$importer->init($organization);
|
||||
$data = file_get_contents($zipPath);
|
||||
|
||||
// Act
|
||||
$importer->importData($data);
|
||||
|
||||
// Assert
|
||||
$this->checkTestScenarioAfterImportExcludingTimeEntries();
|
||||
}
|
||||
|
||||
public function test_import_of_test_file_twice_succeeds(): void
|
||||
{
|
||||
// Arrange
|
||||
$zipPath = $this->createTestZip('toggl_data_import_test_1');
|
||||
$organization = Organization::factory()->create();
|
||||
$importer = new TogglDataImporter();
|
||||
$importer->init($organization);
|
||||
$data = file_get_contents($zipPath);
|
||||
$importer->importData($data);
|
||||
$importer = new TogglDataImporter();
|
||||
$importer->init($organization);
|
||||
|
||||
// Act
|
||||
$importer->importData($data);
|
||||
|
||||
// Assert
|
||||
$this->checkTestScenarioAfterImportExcludingTimeEntries();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Service\Import\Importer;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Models\TimeEntry;
|
||||
use App\Service\Import\Importers\TogglTimeEntriesImporter;
|
||||
|
||||
class TogglTimeEntriesImporterTest extends ImporterTestAbstract
|
||||
{
|
||||
public function test_import_of_test_file_succeeds(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create();
|
||||
$importer = new TogglTimeEntriesImporter();
|
||||
$importer->init($organization);
|
||||
$data = file_get_contents(storage_path('tests/toggl_time_entries_import_test_1.csv'));
|
||||
|
||||
// Act
|
||||
$importer->importData($data, []);
|
||||
|
||||
// Assert
|
||||
$testScenario = $this->checkTestScenarioAfterImportExcludingTimeEntries();
|
||||
$timeEntries = TimeEntry::all();
|
||||
$this->assertCount(2, $timeEntries);
|
||||
$timeEntry1 = $timeEntries->firstWhere('description', '');
|
||||
$this->assertNotNull($timeEntry1);
|
||||
$this->assertSame('', $timeEntry1->description);
|
||||
$this->assertSame('2024-03-04 10:23:52', $timeEntry1->start->toDateTimeString());
|
||||
$this->assertSame('2024-03-04 10:23:52', $timeEntry1->end->toDateTimeString());
|
||||
$this->assertFalse($timeEntry1->billable);
|
||||
$this->assertSame([$testScenario->tag1->getKey(), $testScenario->tag2->getKey()], $timeEntry1->tags);
|
||||
$timeEntry2 = $timeEntries->firstWhere('description', 'Working hard');
|
||||
$this->assertNotNull($timeEntry2);
|
||||
$this->assertSame('Working hard', $timeEntry2->description);
|
||||
$this->assertSame('2024-03-04 10:23:00', $timeEntry2->start->toDateTimeString());
|
||||
$this->assertSame('2024-03-04 11:23:01', $timeEntry2->end->toDateTimeString());
|
||||
$this->assertTrue($timeEntry2->billable);
|
||||
$this->assertSame([], $timeEntry2->tags);
|
||||
}
|
||||
|
||||
public function test_import_of_test_file_twice_succeeds(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create();
|
||||
$importer = new TogglTimeEntriesImporter();
|
||||
$importer->init($organization);
|
||||
$data = file_get_contents(storage_path('tests/toggl_time_entries_import_test_1.csv'));
|
||||
$importer->importData($data, []);
|
||||
$importer = new TogglTimeEntriesImporter();
|
||||
$importer->init($organization);
|
||||
|
||||
// Act
|
||||
$importer->importData($data, []);
|
||||
|
||||
// Assert
|
||||
$testScenario = $this->checkTestScenarioAfterImportExcludingTimeEntries();
|
||||
$timeEntries = TimeEntry::all();
|
||||
$this->assertCount(4, $timeEntries);
|
||||
$timeEntry1 = $timeEntries->firstWhere('description', '');
|
||||
$this->assertNotNull($timeEntry1);
|
||||
$this->assertSame('', $timeEntry1->description);
|
||||
$this->assertSame('2024-03-04 10:23:52', $timeEntry1->start->toDateTimeString());
|
||||
$this->assertSame('2024-03-04 10:23:52', $timeEntry1->end->toDateTimeString());
|
||||
$this->assertFalse($timeEntry1->billable);
|
||||
$this->assertSame([$testScenario->tag1->getKey(), $testScenario->tag2->getKey()], $timeEntry1->tags);
|
||||
$timeEntry2 = $timeEntries->firstWhere('description', 'Working hard');
|
||||
$this->assertNotNull($timeEntry2);
|
||||
$this->assertSame('Working hard', $timeEntry2->description);
|
||||
$this->assertSame('2024-03-04 10:23:00', $timeEntry2->start->toDateTimeString());
|
||||
$this->assertSame('2024-03-04 11:23:01', $timeEntry2->end->toDateTimeString());
|
||||
$this->assertTrue($timeEntry2->billable);
|
||||
$this->assertSame([], $timeEntry2->tags);
|
||||
}
|
||||
}
|
||||
37
tests/Unit/Service/UserServiceTest.php
Normal file
37
tests/Unit/Service/UserServiceTest.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Service;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Models\TimeEntry;
|
||||
use App\Models\User;
|
||||
use App\Service\UserService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class UserServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_assign_organization_entities_to_different_user(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create();
|
||||
$otherUser = User::factory()->create();
|
||||
$fromUser = User::factory()->create();
|
||||
$toUser = User::factory()->create();
|
||||
TimeEntry::factory()->forOrganization($organization)->forUser($otherUser)->createMany(3);
|
||||
TimeEntry::factory()->forOrganization($organization)->forUser($fromUser)->createMany(3);
|
||||
|
||||
// Act
|
||||
$userService = app(UserService::class);
|
||||
$userService->assignOrganizationEntitiesToDifferentUser($organization, $fromUser, $toUser);
|
||||
|
||||
// Assert
|
||||
$this->assertSame(3, TimeEntry::query()->whereBelongsTo($toUser, 'user')->count());
|
||||
$this->assertSame(3, TimeEntry::query()->whereBelongsTo($otherUser, 'user')->count());
|
||||
$this->assertSame(0, TimeEntry::query()->whereBelongsTo($fromUser, 'user')->count());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user