Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an end date to the course assignment to the user #321

Merged
merged 2 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddEndDateColumnToCourseUserTable extends Migration
{
public function up(): void
{
Schema::table('course_user', function (Blueprint $table) {
$table->dateTime('end_date')->nullable();
});
}

public function down(): void
{
Schema::table('course_user', function (Blueprint $table) {
$table->dropColumn('end_date');
});
}
}
1 change: 1 addition & 0 deletions src/Http/Resources/ProgressResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public function toArray($request)
'start_date' => $this->getResource()->getStartDate(),
'finish_date' => $this->getResource()->isFinished() ? $this->getResource()->getFinishDate() : null,
'deadline' => $this->getResource()->getDeadline(),
'end_date' => $this->getResource()->getEndDate(),
'total_spent_time' => $this->getResource()->getTotalSpentTime() ?? 0,
];
}
Expand Down
6 changes: 5 additions & 1 deletion src/Models/Course.php
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,11 @@ public function hasUser(CoreUser|User $user): bool
->where('user_id', $user->getKey())
->exists();

return $this->users()->where('users.id', $user->getKey())->exists() || $inGroup;
return $this->users()
->where('users.id', $user->getKey())
->where(fn(Builder $query) => $query->whereNull('end_date')->orWhereDate('end_date', '>=', Carbon::now()))
->exists()
|| $inGroup;
}

private function getChildGroups(array $groupIds): array
Expand Down
1 change: 1 addition & 0 deletions src/Models/CourseUserPivot.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class CourseUserPivot extends Pivot

protected $casts = [
'deadline' => 'datetime',
'end_date' => 'datetime',
];

public function user(): BelongsTo
Expand Down
1 change: 1 addition & 0 deletions src/Policies/CoursesPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ public function attend(?User $user, Course $course): bool
if ($user->can(CoursesPermissionsEnum::COURSE_ATTEND_OWNED)) {
return $course->hasAuthor($user);
}

return $course->is_published && $course->hasUser($user);
}

Expand Down
16 changes: 14 additions & 2 deletions src/ValueObjects/CourseProgressCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class CourseProgressCollection extends ValueObject implements ValueObjectContrac
private ?Carbon $startDate;
private ?Carbon $finishDate;
private ?Carbon $deadline;
private ?Carbon $endDate;

public function __construct(
CourseProgressRepositoryContract $courseProgressRepositoryContract
Expand All @@ -50,6 +51,7 @@ public function build(Authenticatable $user, Course $course): self
$this->finishDate = null;
$this->pivot = CourseUserPivot::query()->where('user_id', $user->getKey())->where('course_id', $course->getKey())->first();
$this->deadline = $this->pivot ? $this->pivot->deadline : null;
$this->endDate = $this->pivot ? $this->pivot->end_date : null;
$this->topics = $this->getActiveTopicIdsFromCourses();
$this->progress = $this->buildProgress();

Expand Down Expand Up @@ -205,9 +207,19 @@ public function getDeadline(): ?Carbon
return $this->deadline;
}

public function getEndDate(): ?Carbon
{
return $this->endDate;
}

public function afterDeadline(): bool
{
return $this->getDeadline() ? Carbon::now()->greaterThanOrEqualTo($this->getDeadline()) : false;
return $this->getDeadline() && Carbon::now()->greaterThanOrEqualTo($this->getDeadline());
}

public function afterEndDate(): bool
{
return $this->getEndDate() && Carbon::now()->greaterThanOrEqualTo($this->getEndDate());
}

public function toArray(): array
Expand All @@ -217,7 +229,7 @@ public function toArray(): array

public function topicCanBeProgressed(Topic $topic): bool
{
return $this->courseCanBeProgressed() && $topic->active;
return $this->courseCanBeProgressed() && $topic->active && !$this->afterEndDate();
}

public function courseCanBeProgressed(): bool
Expand Down
22 changes: 22 additions & 0 deletions tests/APIs/CourseProgramApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,26 @@ public function testShowCourseProgramForLessonAvailableBetweenDates(): void
'topicable' => null,
]);
}

public function testCannotShowCourseProgramWhenEndDateIsOverdue(): void
{
$student = $this->makeStudent();
$course = Course::factory()->state(['status' => CourseStatusEnum::PUBLISHED])->create();
$course->users()->attach($student, ['end_date' => Carbon::now()->subDay()]);

$this->actingAs($student, 'api')
->getJson('api/courses/' . $course->getKey() . '/program')
->assertForbidden();
}

public function testCannotShowCourseProgramWhenEndDateIsCurrent(): void
{
$student = $this->makeStudent();
$course = Course::factory()->state(['status' => CourseStatusEnum::PUBLISHED])->create();
$course->users()->attach($student, ['end_date' => Carbon::now()->addDay()]);

$this->actingAs($student, 'api')
->getJson('api/courses/' . $course->getKey() . '/program')
->assertOk();
}
}
153 changes: 142 additions & 11 deletions tests/APIs/CourseProgressApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class CourseProgressApiTest extends TestCase
use CreatesUsers, WithFaker, ProgressConfigurable, MakeServices;
use DatabaseTransactions;

public function test_show_progress_courses()
public function test_show_progress_courses(): void
{
$user = User::factory()->create();
$course = Course::factory()->create(['status' => CourseStatusEnum::PUBLISHED]);
Expand Down Expand Up @@ -74,7 +74,7 @@ public function test_show_progress_courses()
]);
}

public function test_show_progress_courses_paginated_ordered()
public function test_show_progress_courses_paginated_ordered(): void
{
$user = User::factory()->create();

Expand Down Expand Up @@ -200,7 +200,7 @@ public function test_show_progress_courses_paginated_ordered()
$this->assertTrue($this->response->json('data.3.course.id') === $course1->getKey());
}

public function test_show_progress_courses_paginated_filtered_planned()
public function test_show_progress_courses_paginated_filtered_planned(): void
{
$user = User::factory()->create();

Expand Down Expand Up @@ -269,7 +269,7 @@ public function test_show_progress_courses_paginated_filtered_planned()
]);
}

public function test_show_progress_courses_paginated_filtered_finished()
public function test_show_progress_courses_paginated_filtered_finished(): void
{
$user = User::factory()->create();

Expand Down Expand Up @@ -325,7 +325,7 @@ public function test_show_progress_courses_paginated_filtered_finished()
]);
}

public function test_show_progress_courses_paginated_filtered_started()
public function test_show_progress_courses_paginated_filtered_started(): void
{
$user = User::factory()->create();

Expand Down Expand Up @@ -395,7 +395,7 @@ public function test_show_progress_courses_paginated_filtered_started()
]);
}

public function test_show_progress_courses_ordered_by_latest_purchased()
public function test_show_progress_courses_ordered_by_latest_purchased(): void
{
$user = User::factory()->create();
$courseOne = Course::factory()->create(['status' => CourseStatusEnum::PUBLISHED]);
Expand All @@ -415,7 +415,7 @@ public function test_show_progress_courses_ordered_by_latest_purchased()
$this->assertEquals($courseOne->getKey(), $this->response->json('data.1.course.id'));
}

public function test_show_progress_course_from_group()
public function test_show_progress_course_from_group(): void
{
$user = User::factory()->create();
$course = Course::factory()->create(['status' => CourseStatusEnum::PUBLISHED]);
Expand Down Expand Up @@ -464,6 +464,51 @@ public function test_show_progress_course_from_parent_group(): void
]);
}

public function test_show_progress_courses_with_end_date(): void
{
$student1 = $this->makeStudent();
$student2 = $this->makeStudent();
$endDate = Carbon::now()->startOfDay()->subDay();
$course = Course::factory()->create(['status' => CourseStatusEnum::PUBLISHED]);

$student1->courses()->attach($course->getKey(), ['end_date' => $endDate]);
$student2->courses()->attach($course->getKey());

$this->actingAs($student1, 'api')
->getJson('/api/courses/progress')
->assertStatus(200)
->assertJsonFragment([
'end_date' => $endDate
])
->assertJsonStructure([
'data' => [[
'course',
'progress',
'categories',
'tags',
'finish_date',
'end_date'
]]
]);

$this->actingAs($student2, 'api')
->getJson('/api/courses/progress')
->assertStatus(200)
->assertJsonFragment([
'end_date' => null
])
->assertJsonStructure([
'data' => [[
'course',
'progress',
'categories',
'tags',
'finish_date',
'end_date'
]]
]);
}

public function test_update_course_progress(): void
{
Mail::fake();
Expand Down Expand Up @@ -682,7 +727,7 @@ public function test_verify_course_started(): void
Event::assertDispatched(CourseAccessStarted::class);
}

public function test_ping_progress_course()
public function test_ping_progress_course(): void
{
/** @var User $user */
$user = User::factory()->create();
Expand Down Expand Up @@ -753,7 +798,7 @@ public function test_ping_should_not_dispatch_topic_finished_event_again(): void
Event::assertNotDispatched(TopicFinished::class);
}

public function test_ping_complete_topic()
public function test_ping_complete_topic(): void
{
/** @var User $user */
$user = User::factory()->create();
Expand Down Expand Up @@ -812,6 +857,92 @@ public function test_ping_complete_topic()
]);
}

public function test_ping_complete_topic_when_end_date_is_overdue(): void
{
$user = $this->makeStudent();
$course = Course::factory()->create(['status' => CourseStatusEnum::PUBLISHED]);
$lesson = Lesson::factory()->create(['course_id' => $course->getKey()]);
$topic = Topic::factory()->create([
'active' => true,
'lesson_id' => $lesson->getKey(),
]);

$user->courses()->attach($course->getKey(), ['end_date' => Carbon::now()->subDay()]);

CourseProgress::create([
'user_id' => $user->getKey(),
'topic_id' => $topic->getKey(),
'status' => ProgressStatus::COMPLETE,
'seconds' => 10,
]);

$this->actingAs($user, 'api')
->putJson('/api/courses/progress/' . $topic->getKey() . '/ping')
->assertOk()
->assertJsonFragment([
'status' => true
]);

sleep(5);

$this->actingAs($user, 'api')
->putJson('/api/courses/progress/' . $topic->getKey() . '/ping')
->assertOk()
->assertJsonFragment([
'status' => true
]);

$this->assertDatabaseHas('course_progress', [
'user_id' => $user->getKey(),
'topic_id' => $topic->getKey(),
'status' => ProgressStatus::COMPLETE,
'seconds' => 10,
]);
}

public function test_ping_complete_topic_when_end_date_is_current(): void
{
$user = $this->makeStudent();
$course = Course::factory()->create(['status' => CourseStatusEnum::PUBLISHED]);
$lesson = Lesson::factory()->create(['course_id' => $course->getKey()]);
$topic = Topic::factory()->create([
'active' => true,
'lesson_id' => $lesson->getKey(),
]);

$user->courses()->syncWithPivotValues($course->getKey(), ['end_date' => Carbon::now()->addDay()]);

CourseProgress::create([
'user_id' => $user->getKey(),
'topic_id' => $topic->getKey(),
'status' => ProgressStatus::COMPLETE,
'seconds' => 10,
]);

$this->actingAs($user, 'api')
->putJson('/api/courses/progress/' . $topic->getKey() . '/ping')
->assertOk()
->assertJsonFragment([
'status' => true
]);

sleep(5);

$this->actingAs($user, 'api')
->putJson('/api/courses/progress/' . $topic->getKey() . '/ping')
->assertOk()
->assertJsonFragment([
'status' => true
]);

$this->assertDatabaseHas('course_progress', [
'user_id' => $user->getKey(),
'topic_id' => $topic->getKey(),
'status' => ProgressStatus::COMPLETE,
'seconds' => 15,
]);
}

public function test_adding_new_topic_will_reset_finished_status(): void
{
Mail::fake();
Expand Down Expand Up @@ -859,7 +990,7 @@ public function test_adding_new_topic_will_reset_finished_status(): void
$this->assertFalse($courseProgress->isFinished());
}

public function test_active_to()
public function test_active_to(): void
{
$user = User::factory()->create();
$course = Course::factory()->create(['status' => CourseStatusEnum::PUBLISHED, 'active_to' => Carbon::now()->subDay()]);
Expand Down Expand Up @@ -904,7 +1035,7 @@ public function test_active_to()
$this->assertEquals($this->response->json('data.0.course.active_to'), $this->response->json('data.0.deadline'));
}

public function test_deadline()
public function test_deadline(): void
{
$user = User::factory()->create();

Expand Down
Loading