1172 lines
46 KiB
PHP
1172 lines
46 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Feature\Academy;
|
|
|
|
use App\Http\Middleware\ConditionalValidateCsrfToken;
|
|
use App\Http\Middleware\HandleInertiaRequests;
|
|
use App\Models\AcademyAiComparisonResult;
|
|
use App\Models\AcademyChallenge;
|
|
use App\Models\AcademyCourse;
|
|
use App\Models\AcademyCourseEnrollment;
|
|
use App\Models\AcademyCourseLesson;
|
|
use App\Models\AcademyCourseSection;
|
|
use App\Models\AcademyLesson;
|
|
use App\Models\AcademyLessonBlock;
|
|
use App\Models\AcademyLessonProgress;
|
|
use App\Models\AcademyPromptTemplate;
|
|
use App\Models\Artwork;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Inertia\Testing\AssertableInertia;
|
|
use Tests\TestCase;
|
|
|
|
final class AcademyFeatureTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->withoutMiddleware(ConditionalValidateCsrfToken::class);
|
|
}
|
|
|
|
public function test_academy_homepage_loads_when_enabled(): void
|
|
{
|
|
$this->get('/academy')
|
|
->assertOk()
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->component('Academy/Index')
|
|
->where('featureFlags.paymentsEnabled', false));
|
|
}
|
|
|
|
public function test_academy_routes_are_hidden_when_feature_is_disabled(): void
|
|
{
|
|
config(['academy.enabled' => false]);
|
|
|
|
$this->get('/academy')->assertNotFound();
|
|
$this->get('/academy/lessons')->assertNotFound();
|
|
$this->get('/academy/courses')->assertNotFound();
|
|
}
|
|
|
|
public function test_published_course_index_and_show_render(): void
|
|
{
|
|
$course = AcademyCourse::query()->create([
|
|
'title' => 'AI Foundations',
|
|
'slug' => 'ai-foundations',
|
|
'excerpt' => 'A guided course.',
|
|
'description' => 'Course description',
|
|
'cover_image' => 'academy/lessons/covers/course-cover.webp',
|
|
'teaser_image' => 'academy/lessons/covers/course-teaser.webp',
|
|
'access_level' => 'free',
|
|
'difficulty' => 'beginner',
|
|
'status' => 'published',
|
|
'is_featured' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$section = AcademyCourseSection::query()->create([
|
|
'course_id' => $course->id,
|
|
'title' => 'Getting started',
|
|
'slug' => 'getting-started',
|
|
'order_num' => 0,
|
|
'is_visible' => true,
|
|
]);
|
|
|
|
$lesson = AcademyLesson::query()->create([
|
|
'title' => 'Course Lesson',
|
|
'slug' => 'course-lesson',
|
|
'excerpt' => 'Lesson in a course.',
|
|
'content' => 'Course lesson content',
|
|
'cover_image' => 'academy/lessons/covers/lesson-cover.webp',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
AcademyCourseLesson::query()->create([
|
|
'course_id' => $course->id,
|
|
'section_id' => $section->id,
|
|
'lesson_id' => $lesson->id,
|
|
'order_num' => 0,
|
|
'is_required' => true,
|
|
]);
|
|
|
|
$this->get(route('academy.courses.index'))
|
|
->assertOk()
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->component('Academy/CoursesIndex')
|
|
->where('items.data.0.slug', 'ai-foundations')
|
|
->where('seo.json_ld.0.@type', 'CollectionPage')
|
|
->where('seo.json_ld.0.mainEntity.@type', 'ItemList')
|
|
->where('seo.json_ld.0.mainEntity.itemListElement.0.item.@type', 'Course')
|
|
->where('seo.json_ld.0.mainEntity.itemListElement.0.item.name', 'AI Foundations')
|
|
->where('seo.json_ld.1.@type', 'BreadcrumbList'));
|
|
|
|
$this->get(route('academy.courses.show', ['course' => $course->slug]))
|
|
->assertOk()
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->component('Academy/CoursesShow')
|
|
->where('course.slug', 'ai-foundations')
|
|
->where('course.cover_image', 'academy/lessons/covers/course-cover.webp')
|
|
->where('course.teaser_image', 'academy/lessons/covers/course-teaser.webp')
|
|
->where('seo.json_ld.0.@type', 'ImageObject')
|
|
->where('seo.json_ld.0.license', route('terms-of-service'))
|
|
->where('seo.json_ld.1.@type', 'Course')
|
|
->where('seo.json_ld.1.name', 'AI Foundations — Skinbase Academy')
|
|
->where('seo.json_ld.1.isAccessibleForFree', true)
|
|
->where('seo.json_ld.1.educationalLevel', 'Beginner')
|
|
->where('seo.json_ld.1.hasCourseInstance.0.name', 'Course Lesson')
|
|
->where('seo.json_ld.2.@type', 'BreadcrumbList')
|
|
->where('seo.json_ld.2.itemListElement.1.name', 'Academy')
|
|
->where('seo.json_ld.2.itemListElement.2.name', 'Courses')
|
|
->where('sections.0.slug', 'getting-started')
|
|
->where('sections.0.lessons.0.slug', 'course-lesson')
|
|
->where('sections.0.lessons.0.order_num', 0)
|
|
->where('sections.0.lessons.0.cover_image', 'academy/lessons/covers/lesson-cover.webp'));
|
|
}
|
|
|
|
public function test_course_start_redirects_to_first_lesson_and_creates_enrollment(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$course = AcademyCourse::query()->create([
|
|
'title' => 'Workflow Course',
|
|
'slug' => 'workflow-course',
|
|
'excerpt' => 'A workflow course.',
|
|
'access_level' => 'free',
|
|
'difficulty' => 'beginner',
|
|
'status' => 'published',
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$lesson = AcademyLesson::query()->create([
|
|
'title' => 'First Workflow Lesson',
|
|
'slug' => 'first-workflow-lesson',
|
|
'content' => 'Start here',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
AcademyCourseLesson::query()->create([
|
|
'course_id' => $course->id,
|
|
'lesson_id' => $lesson->id,
|
|
'order_num' => 0,
|
|
'is_required' => true,
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->post(route('academy.courses.start', ['course' => $course->slug]))
|
|
->assertRedirect(route('academy.courses.lessons.show', ['course' => $course->slug, 'lesson' => $lesson->slug]));
|
|
|
|
$this->assertDatabaseHas('academy_course_enrollments', [
|
|
'user_id' => $user->id,
|
|
'course_id' => $course->id,
|
|
'status' => AcademyCourseEnrollment::STATUS_ACTIVE,
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->get(route('academy.courses.lessons.show', ['course' => $course->slug, 'lesson' => $lesson->slug]))
|
|
->assertOk()
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->component('Academy/Show')
|
|
->where('courseContext.slug', 'workflow-course')
|
|
->where('courseContext.completePayload.course_id', $course->id));
|
|
}
|
|
|
|
public function test_course_show_exposes_consistent_global_step_numbers_for_outline(): void
|
|
{
|
|
$course = AcademyCourse::query()->create([
|
|
'title' => 'Ordered Course',
|
|
'slug' => 'ordered-course',
|
|
'excerpt' => 'Check course step order.',
|
|
'access_level' => 'free',
|
|
'difficulty' => 'beginner',
|
|
'status' => 'published',
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$section = AcademyCourseSection::query()->create([
|
|
'course_id' => $course->id,
|
|
'title' => 'Module One',
|
|
'slug' => 'module-one',
|
|
'order_num' => 0,
|
|
'is_visible' => true,
|
|
]);
|
|
|
|
$introLesson = AcademyLesson::query()->create([
|
|
'title' => 'Intro Lesson',
|
|
'slug' => 'intro-lesson',
|
|
'content' => 'Intro content',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'lesson_type' => 'article',
|
|
'lesson_number' => 3,
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$sectionLesson = AcademyLesson::query()->create([
|
|
'title' => 'Section Lesson',
|
|
'slug' => 'section-lesson',
|
|
'content' => 'Section content',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'lesson_type' => 'article',
|
|
'lesson_number' => 1,
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
AcademyCourseLesson::query()->create([
|
|
'course_id' => $course->id,
|
|
'lesson_id' => $introLesson->id,
|
|
'order_num' => 0,
|
|
'is_required' => true,
|
|
]);
|
|
|
|
AcademyCourseLesson::query()->create([
|
|
'course_id' => $course->id,
|
|
'section_id' => $section->id,
|
|
'lesson_id' => $sectionLesson->id,
|
|
'order_num' => 0,
|
|
'is_required' => true,
|
|
]);
|
|
|
|
$this->get(route('academy.courses.show', ['course' => $course->slug]))
|
|
->assertOk()
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->component('Academy/CoursesShow')
|
|
->where('unsectionedLessons.0.title', 'Intro Lesson')
|
|
->where('unsectionedLessons.0.course_step_number', 1)
|
|
->where('unsectionedLessons.0.course_step_label', 'Step 01')
|
|
->where('sections.0.lessons.0.title', 'Section Lesson')
|
|
->where('sections.0.lessons.0.course_step_number', 2)
|
|
->where('sections.0.lessons.0.course_step_label', 'Step 02')
|
|
->where('unsectionedLessons.0.formatted_lesson_number', 'Lesson 03')
|
|
->where('sections.0.lessons.0.formatted_lesson_number', 'Lesson 01'));
|
|
}
|
|
|
|
public function test_course_show_marks_completed_lessons_for_authenticated_user(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
$course = AcademyCourse::query()->create([
|
|
'title' => 'Guided Course',
|
|
'slug' => 'guided-course',
|
|
'excerpt' => 'Track outline completion.',
|
|
'access_level' => 'free',
|
|
'difficulty' => 'beginner',
|
|
'status' => 'published',
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$section = AcademyCourseSection::query()->create([
|
|
'course_id' => $course->id,
|
|
'title' => 'Module One',
|
|
'slug' => 'module-one',
|
|
'order_num' => 0,
|
|
'is_visible' => true,
|
|
]);
|
|
|
|
$lesson = AcademyLesson::query()->create([
|
|
'title' => 'Completed Lesson',
|
|
'slug' => 'completed-lesson',
|
|
'content' => 'Lesson content',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
AcademyCourseLesson::query()->create([
|
|
'course_id' => $course->id,
|
|
'section_id' => $section->id,
|
|
'lesson_id' => $lesson->id,
|
|
'order_num' => 0,
|
|
'is_required' => true,
|
|
]);
|
|
|
|
AcademyLessonProgress::query()->create([
|
|
'user_id' => $user->id,
|
|
'lesson_id' => $lesson->id,
|
|
'completed_at' => now(),
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->get(route('academy.courses.show', ['course' => $course->slug]))
|
|
->assertOk()
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->component('Academy/CoursesShow')
|
|
->where('course.progress.completedRequired', 1)
|
|
->where('sections.0.lessons.0.completed', true));
|
|
}
|
|
|
|
public function test_standalone_lesson_show_exposes_related_courses(): void
|
|
{
|
|
$lesson = AcademyLesson::query()->create([
|
|
'title' => 'Shared Lesson',
|
|
'slug' => 'shared-lesson',
|
|
'content' => 'Shared content',
|
|
'article_cover_image' => 'academy/lessons/covers/shared-lesson.webp',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$course = AcademyCourse::query()->create([
|
|
'title' => 'Related Course',
|
|
'slug' => 'related-course',
|
|
'excerpt' => 'Course tied to this lesson.',
|
|
'access_level' => 'free',
|
|
'difficulty' => 'beginner',
|
|
'status' => 'published',
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
AcademyCourseLesson::query()->create([
|
|
'course_id' => $course->id,
|
|
'lesson_id' => $lesson->id,
|
|
'order_num' => 0,
|
|
'is_required' => true,
|
|
]);
|
|
|
|
$this->get(route('academy.lessons.show', ['slug' => $lesson->slug]))
|
|
->assertOk()
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->component('Academy/Show')
|
|
->where('seo.json_ld.0.@type', 'ImageObject')
|
|
->where('seo.json_ld.0.creditText', 'Skinbase')
|
|
->where('seo.json_ld.1.@type', 'Article')
|
|
->where('seo.json_ld.1.headline', 'Shared Lesson — Skinbase Academy')
|
|
->where('seo.json_ld.1.articleSection', 'Academy')
|
|
->where('seo.json_ld.2.@type', 'BreadcrumbList')
|
|
->where('relatedCourses.0.slug', 'related-course'));
|
|
}
|
|
|
|
public function test_course_lesson_show_exposes_article_breadcrumb_and_image_license_schema(): void
|
|
{
|
|
$course = AcademyCourse::query()->create([
|
|
'title' => 'AI Art Basics',
|
|
'slug' => 'ai-art-basics',
|
|
'excerpt' => 'Course excerpt',
|
|
'access_level' => 'free',
|
|
'difficulty' => 'beginner',
|
|
'status' => 'published',
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$section = AcademyCourseSection::query()->create([
|
|
'course_id' => $course->id,
|
|
'title' => 'Foundations',
|
|
'slug' => 'foundations',
|
|
'order_num' => 0,
|
|
'is_visible' => true,
|
|
]);
|
|
|
|
$lesson = AcademyLesson::query()->create([
|
|
'title' => 'What Is AI-Assisted Digital Art',
|
|
'slug' => 'what-is-ai-assisted-digital-art',
|
|
'excerpt' => 'Structured data test lesson.',
|
|
'content' => 'Lesson body',
|
|
'article_cover_image' => 'academy/lessons/covers/ai-assist.webp',
|
|
'tags' => ['ai-art', 'workflow'],
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
AcademyCourseLesson::query()->create([
|
|
'course_id' => $course->id,
|
|
'section_id' => $section->id,
|
|
'lesson_id' => $lesson->id,
|
|
'order_num' => 0,
|
|
'is_required' => true,
|
|
]);
|
|
|
|
$this->get(route('academy.courses.lessons.show', ['course' => $course->slug, 'lesson' => $lesson->slug]))
|
|
->assertOk()
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->component('Academy/Show')
|
|
->where('seo.json_ld.0.@type', 'ImageObject')
|
|
->where('seo.json_ld.0.license', route('terms-of-service'))
|
|
->where('seo.json_ld.0.creditText', 'Skinbase')
|
|
->where('seo.json_ld.1.@type', 'Article')
|
|
->where('seo.json_ld.1.headline', 'What Is AI-Assisted Digital Art — AI Art Basics')
|
|
->where('seo.json_ld.1.articleSection', 'AI Art Basics')
|
|
->where('seo.json_ld.1.keywords.0', 'ai-art')
|
|
->where('seo.json_ld.2.@type', 'BreadcrumbList')
|
|
->where('seo.json_ld.2.itemListElement.3.name', 'AI Art Basics')
|
|
->where('seo.json_ld.2.itemListElement.4.name', 'What Is AI-Assisted Digital Art'));
|
|
}
|
|
|
|
public function test_free_lesson_is_visible_to_guest(): void
|
|
{
|
|
$lesson = AcademyLesson::query()->create([
|
|
'title' => 'Free Lesson',
|
|
'slug' => 'free-lesson',
|
|
'excerpt' => 'Visible to guests.',
|
|
'content' => 'Free lesson content',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'lesson_type' => 'article',
|
|
'article_cover_image' => 'academy/lessons/covers/article-cover.webp',
|
|
'tags' => ['workflow', 'publishing'],
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$this->get(route('academy.lessons.show', ['slug' => $lesson->slug]))
|
|
->assertOk()
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->component('Academy/Show')
|
|
->where('item.locked', false)
|
|
->where('item.content', 'Free lesson content')
|
|
->where('item.article_cover_image', 'academy/lessons/covers/article-cover.webp')
|
|
->where('item.tags.0', 'workflow')
|
|
->where('item.tags.1', 'publishing')
|
|
->where('item.article_cover_image_url', fn (?string $value) => is_string($value) && str_contains($value, 'academy/lessons/covers/article-cover.webp')));
|
|
}
|
|
|
|
public function test_creator_lesson_is_locked_for_regular_user(): void
|
|
{
|
|
$lesson = AcademyLesson::query()->create([
|
|
'title' => 'Creator Lesson',
|
|
'slug' => 'creator-lesson',
|
|
'excerpt' => 'Preview only',
|
|
'content' => 'Creator only lesson content',
|
|
'difficulty' => 'intermediate',
|
|
'access_level' => 'creator',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$user = User::factory()->create();
|
|
|
|
$this->actingAs($user)
|
|
->get(route('academy.lessons.show', ['slug' => $lesson->slug]))
|
|
->assertOk()
|
|
->assertDontSee('Creator only lesson content')
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->where('item.locked', true)
|
|
->where('item.content', null));
|
|
}
|
|
|
|
public function test_pro_lesson_is_locked_for_creator_user(): void
|
|
{
|
|
$lesson = AcademyLesson::query()->create([
|
|
'title' => 'Pro Lesson',
|
|
'slug' => 'pro-lesson',
|
|
'excerpt' => 'Pro preview',
|
|
'content' => 'Pro only lesson content',
|
|
'difficulty' => 'pro',
|
|
'access_level' => 'pro',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$creator = User::factory()->create(['role' => 'academy_creator']);
|
|
|
|
$this->actingAs($creator)
|
|
->get(route('academy.lessons.show', ['slug' => $lesson->slug]))
|
|
->assertOk()
|
|
->assertDontSee('Pro only lesson content')
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->where('item.locked', true)
|
|
->where('item.content', null));
|
|
}
|
|
|
|
public function test_premium_prompt_full_text_is_not_exposed_to_unauthorized_users(): void
|
|
{
|
|
$prompt = AcademyPromptTemplate::query()->create([
|
|
'title' => 'Creator Prompt',
|
|
'slug' => 'creator-prompt',
|
|
'excerpt' => 'Locked preview.',
|
|
'prompt' => 'SECRET PREMIUM PROMPT STRING',
|
|
'negative_prompt' => 'SECRET NEGATIVE STRING',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'creator',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$this->get(route('academy.prompts.show', ['slug' => $prompt->slug]))
|
|
->assertOk()
|
|
->assertDontSee('SECRET PREMIUM PROMPT STRING')
|
|
->assertDontSee('SECRET NEGATIVE STRING')
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->where('item.locked', true)
|
|
->where('item.prompt', null)
|
|
->where('item.negative_prompt', null));
|
|
|
|
$version = app(HandleInertiaRequests::class)
|
|
->version(Request::create(route('academy.prompts.show', ['slug' => $prompt->slug]), 'GET'));
|
|
|
|
$this->withHeaders([
|
|
'X-Inertia' => 'true',
|
|
'X-Requested-With' => 'XMLHttpRequest',
|
|
'X-Inertia-Version' => $version,
|
|
])->get(route('academy.prompts.show', ['slug' => $prompt->slug]))
|
|
->assertOk()
|
|
->assertJsonPath('props.item.locked', true)
|
|
->assertJsonPath('props.item.prompt', null)
|
|
->assertJsonPath('props.item.negative_prompt', null)
|
|
->assertDontSee('SECRET PREMIUM PROMPT STRING')
|
|
->assertDontSee('SECRET NEGATIVE STRING');
|
|
}
|
|
|
|
public function test_authorized_user_can_view_premium_prompt(): void
|
|
{
|
|
$prompt = AcademyPromptTemplate::query()->create([
|
|
'title' => 'Creator Prompt',
|
|
'slug' => 'creator-prompt-allowed',
|
|
'excerpt' => 'Full prompt visible.',
|
|
'prompt' => 'VISIBLE PREMIUM PROMPT',
|
|
'negative_prompt' => 'VISIBLE NEGATIVE PROMPT',
|
|
'tool_notes' => [[
|
|
'provider' => 'ChatGPT',
|
|
'model_name' => '4o Image',
|
|
'image_path' => 'academy/lessons/body/cc/dd/chatgpt-comparison.webp',
|
|
'settings' => 'ChatGPT image generation in 16:9 with cinematic mode.',
|
|
'best_for' => 'Fast ideation and art direction.',
|
|
'score' => 8,
|
|
'active' => true,
|
|
]],
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'creator',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$creator = User::factory()->create(['role' => 'academy_creator']);
|
|
|
|
$this->actingAs($creator)
|
|
->get(route('academy.prompts.show', ['slug' => $prompt->slug]))
|
|
->assertOk()
|
|
->assertSee('VISIBLE PREMIUM PROMPT')
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->where('item.locked', false)
|
|
->where('item.prompt', 'VISIBLE PREMIUM PROMPT')
|
|
->where('item.tool_notes.0.provider', 'ChatGPT')
|
|
->where('item.tool_notes.0.model_name', '4o Image')
|
|
->where('item.tool_notes.0.image_path', 'academy/lessons/body/cc/dd/chatgpt-comparison.webp')
|
|
->where('item.tool_notes.0.score', 8));
|
|
}
|
|
|
|
public function test_public_lesson_payload_includes_active_ai_comparison_block_and_hides_inactive_results(): void
|
|
{
|
|
$lesson = AcademyLesson::query()->create([
|
|
'title' => 'Free Lesson With Comparison',
|
|
'slug' => 'free-lesson-with-comparison',
|
|
'excerpt' => 'Visible to guests.',
|
|
'content' => 'Free lesson content',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
$block = AcademyLessonBlock::query()->create([
|
|
'lesson_id' => $lesson->id,
|
|
'type' => 'ai_comparison',
|
|
'title' => 'Same Prompt, Different AI Models',
|
|
'payload' => [
|
|
'title' => 'Same Prompt, Different AI Models',
|
|
'intro' => 'We used the same prompt in multiple tools.',
|
|
'prompt' => 'A peaceful fantasy forest wallpaper.',
|
|
'negative_prompt' => 'text, watermark',
|
|
'aspect_ratio' => '16:9',
|
|
'criteria' => ['Composition', 'Lighting'],
|
|
],
|
|
'sort_order' => 0,
|
|
'active' => true,
|
|
]);
|
|
AcademyAiComparisonResult::query()->create([
|
|
'lesson_block_id' => $block->id,
|
|
'provider' => 'OpenAI',
|
|
'model_name' => 'ChatGPT Images',
|
|
'image_path' => 'academy/lessons/body/aa/bb/example.webp',
|
|
'strengths' => 'Strong composition',
|
|
'score' => 9,
|
|
'sort_order' => 0,
|
|
'active' => true,
|
|
]);
|
|
AcademyAiComparisonResult::query()->create([
|
|
'lesson_block_id' => $block->id,
|
|
'provider' => 'Google',
|
|
'model_name' => 'Gemini',
|
|
'image_path' => 'academy/lessons/body/aa/bb/example-2.webp',
|
|
'score' => 7,
|
|
'sort_order' => 1,
|
|
'active' => false,
|
|
]);
|
|
|
|
$this->get(route('academy.lessons.show', ['slug' => $lesson->slug]))
|
|
->assertOk()
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->component('Academy/Show')
|
|
->where('item.blocks.0.payload.prompt', 'A peaceful fantasy forest wallpaper.')
|
|
->has('item.blocks.0.comparison_results', 1)
|
|
->where('item.blocks.0.comparison_results.0.model_name', 'ChatGPT Images'));
|
|
}
|
|
|
|
public function test_public_lesson_with_sparse_ai_comparison_block_still_renders_payload(): void
|
|
{
|
|
$lesson = AcademyLesson::query()->create([
|
|
'title' => 'Sparse Comparison Lesson',
|
|
'slug' => 'sparse-comparison-lesson',
|
|
'excerpt' => 'Sparse block test.',
|
|
'content' => 'Free lesson content',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
AcademyLessonBlock::query()->create([
|
|
'lesson_id' => $lesson->id,
|
|
'type' => 'ai_comparison',
|
|
'title' => 'Prompt only block',
|
|
'payload' => [
|
|
'title' => 'Prompt only block',
|
|
'prompt' => 'A fantasy forest at sunrise.',
|
|
],
|
|
'sort_order' => 0,
|
|
'active' => true,
|
|
]);
|
|
|
|
$this->get(route('academy.lessons.show', ['slug' => $lesson->slug]))
|
|
->assertOk()
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->has('item.blocks', 1)
|
|
->where('item.blocks.0.payload.prompt', 'A fantasy forest at sunrise.')
|
|
->has('item.blocks.0.comparison_results', 0));
|
|
}
|
|
|
|
public function test_lessons_index_sorts_by_course_order_and_exposes_lesson_labels(): void
|
|
{
|
|
AcademyLesson::query()->create([
|
|
'title' => 'Later Lesson',
|
|
'slug' => 'later-lesson',
|
|
'lesson_number' => 2,
|
|
'course_order' => 2,
|
|
'series_name' => 'AI Art Basics',
|
|
'content' => 'Later content',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => now()->subDays(3),
|
|
]);
|
|
|
|
AcademyLesson::query()->create([
|
|
'title' => 'First Lesson',
|
|
'slug' => 'first-lesson',
|
|
'lesson_number' => 1,
|
|
'course_order' => 1,
|
|
'series_name' => 'AI Art Basics',
|
|
'content' => 'First content',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'article_cover_image' => 'academy/lessons/covers/shared-lesson.webp',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$this->get(route('academy.lessons.index'))
|
|
->assertOk()
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->component('Academy/List')
|
|
->where('items.data.0.slug', 'first-lesson')
|
|
->where('items.data.0.formatted_lesson_number', 'Lesson 01')
|
|
->where('items.data.0.lesson_label', 'AI Art Basics · Lesson 01')
|
|
->where('items.data.1.slug', 'later-lesson'));
|
|
}
|
|
|
|
public function test_lesson_show_exposes_ordered_previous_and_next_navigation_within_series(): void
|
|
{
|
|
$publishMoment = Carbon::parse('2026-01-01 10:00:00');
|
|
|
|
$previous = AcademyLesson::query()->create([
|
|
'title' => 'Lesson One',
|
|
'slug' => 'lesson-one',
|
|
'lesson_number' => 1,
|
|
'course_order' => 1,
|
|
'series_name' => 'AI Art Basics',
|
|
'content' => 'Lesson one body',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => $publishMoment->copy(),
|
|
]);
|
|
|
|
$current = AcademyLesson::query()->create([
|
|
'title' => 'Lesson Two',
|
|
'slug' => 'lesson-two',
|
|
'lesson_number' => 2,
|
|
'course_order' => 2,
|
|
'series_name' => 'AI Art Basics',
|
|
'content' => 'Lesson two body',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => $publishMoment->copy()->addMinute(),
|
|
]);
|
|
|
|
$next = AcademyLesson::query()->create([
|
|
'title' => 'Lesson Three',
|
|
'slug' => 'lesson-three',
|
|
'lesson_number' => 3,
|
|
'course_order' => 3,
|
|
'series_name' => 'AI Art Basics',
|
|
'content' => 'Lesson three body',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => $publishMoment->copy()->addMinutes(2),
|
|
]);
|
|
|
|
AcademyLesson::query()->create([
|
|
'title' => 'Other Series Lesson',
|
|
'slug' => 'other-series-lesson',
|
|
'lesson_number' => 99,
|
|
'course_order' => 99,
|
|
'series_name' => 'Other Series',
|
|
'content' => 'Other series body',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => $publishMoment->copy()->addMinutes(3),
|
|
]);
|
|
|
|
$this->get(route('academy.lessons.show', ['slug' => $current->slug]))
|
|
->assertOk()
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->component('Academy/Show')
|
|
->where('item.lesson_label', 'AI Art Basics · Lesson 02')
|
|
->where('previousLesson.slug', $previous->slug)
|
|
->where('nextLesson.slug', $next->slug));
|
|
}
|
|
|
|
public function test_lesson_show_falls_back_to_category_navigation_when_series_name_is_missing(): void
|
|
{
|
|
$categoryId = DB::table('academy_categories')->insertGetId([
|
|
'type' => 'lesson',
|
|
'name' => 'Prompting Basics',
|
|
'slug' => 'prompting-basics',
|
|
'order_num' => 1,
|
|
'active' => true,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
AcademyLesson::query()->create([
|
|
'title' => 'Category Lesson One',
|
|
'slug' => 'category-lesson-one',
|
|
'category_id' => $categoryId,
|
|
'lesson_number' => 1,
|
|
'course_order' => 1,
|
|
'content' => 'One',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => now()->subDays(2),
|
|
]);
|
|
|
|
$current = AcademyLesson::query()->create([
|
|
'title' => 'Category Lesson Two',
|
|
'slug' => 'category-lesson-two',
|
|
'category_id' => $categoryId,
|
|
'lesson_number' => 2,
|
|
'course_order' => 2,
|
|
'content' => 'Two',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => now()->subDay(),
|
|
]);
|
|
|
|
$this->get(route('academy.lessons.show', ['slug' => $current->slug]))
|
|
->assertOk()
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->where('previousLesson.slug', 'category-lesson-one')
|
|
->where('nextLesson', null));
|
|
}
|
|
|
|
public function test_lesson_payload_hides_label_when_lesson_number_is_missing(): void
|
|
{
|
|
$lesson = AcademyLesson::query()->create([
|
|
'title' => 'Unnumbered Lesson',
|
|
'slug' => 'unnumbered-lesson',
|
|
'series_name' => 'AI Art Basics',
|
|
'content' => 'Lesson body',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$this->get(route('academy.lessons.show', ['slug' => $lesson->slug]))
|
|
->assertOk()
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->where('item.formatted_lesson_number', null)
|
|
->where('item.lesson_label', null));
|
|
}
|
|
|
|
public function test_logged_in_user_can_mark_lesson_completed(): void
|
|
{
|
|
$lesson = AcademyLesson::query()->create([
|
|
'title' => 'Trackable Lesson',
|
|
'slug' => 'trackable-lesson',
|
|
'content' => 'Track this lesson',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$user = User::factory()->create();
|
|
|
|
$this->actingAs($user)
|
|
->postJson(route('academy.lessons.complete', ['lesson' => $lesson->id]))
|
|
->assertOk()
|
|
->assertJsonPath('completed', true);
|
|
|
|
$this->assertDatabaseHas('academy_lesson_progress', [
|
|
'lesson_id' => $lesson->id,
|
|
'user_id' => $user->id,
|
|
]);
|
|
}
|
|
|
|
public function test_logged_in_user_can_save_and_unsave_prompt(): void
|
|
{
|
|
$prompt = AcademyPromptTemplate::query()->create([
|
|
'title' => 'Free Prompt',
|
|
'slug' => 'free-prompt',
|
|
'prompt' => 'Save me',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$user = User::factory()->create();
|
|
|
|
$this->actingAs($user)
|
|
->postJson(route('academy.prompts.save', ['prompt' => $prompt->id]))
|
|
->assertOk()
|
|
->assertJsonPath('saved', true);
|
|
|
|
$this->assertDatabaseHas('academy_saved_prompts', [
|
|
'prompt_template_id' => $prompt->id,
|
|
'user_id' => $user->id,
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->deleteJson(route('academy.prompts.unsave', ['prompt' => $prompt->id]))
|
|
->assertOk()
|
|
->assertJsonPath('saved', false);
|
|
|
|
$this->assertDatabaseMissing('academy_saved_prompts', [
|
|
'prompt_template_id' => $prompt->id,
|
|
'user_id' => $user->id,
|
|
]);
|
|
}
|
|
|
|
public function test_logged_in_user_can_submit_artwork_to_active_challenge(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$artwork = Artwork::factory()->create([
|
|
'user_id' => $user->id,
|
|
'is_public' => true,
|
|
'is_approved' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$challenge = AcademyChallenge::query()->create([
|
|
'title' => 'Open Challenge',
|
|
'slug' => 'open-challenge',
|
|
'access_level' => 'free',
|
|
'status' => 'active',
|
|
'active' => true,
|
|
'starts_at' => now()->subHour(),
|
|
'ends_at' => now()->addDay(),
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->post(route('academy.challenges.submit.store', ['slug' => $challenge->slug]), [
|
|
'artwork_id' => $artwork->id,
|
|
'prompt_used' => 'Prompt used',
|
|
'workflow_notes' => 'Workflow notes',
|
|
'ai_tool_used' => 'ComfyUI',
|
|
'is_ai_generated' => true,
|
|
'is_ai_assisted' => true,
|
|
])
|
|
->assertRedirect(route('academy.challenges.show', ['slug' => $challenge->slug]));
|
|
|
|
$this->assertDatabaseHas('academy_challenge_submissions', [
|
|
'challenge_id' => $challenge->id,
|
|
'user_id' => $user->id,
|
|
'artwork_id' => $artwork->id,
|
|
]);
|
|
}
|
|
|
|
public function test_guest_cannot_view_creator_or_pro_lesson_content(): void
|
|
{
|
|
$creatorLesson = AcademyLesson::query()->create([
|
|
'title' => 'Creator Lesson',
|
|
'slug' => 'guest-creator-lesson',
|
|
'excerpt' => 'Creator preview',
|
|
'content' => 'CREATOR SECRET LESSON BODY',
|
|
'difficulty' => 'advanced',
|
|
'access_level' => 'creator',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$proLesson = AcademyLesson::query()->create([
|
|
'title' => 'Pro Lesson',
|
|
'slug' => 'guest-pro-lesson',
|
|
'excerpt' => 'Pro preview',
|
|
'content' => 'PRO SECRET LESSON BODY',
|
|
'difficulty' => 'pro',
|
|
'access_level' => 'pro',
|
|
'lesson_type' => 'article',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
foreach ([$creatorLesson, $proLesson] as $lesson) {
|
|
$this->get(route('academy.lessons.show', ['slug' => $lesson->slug]))
|
|
->assertOk()
|
|
->assertDontSee($lesson->content)
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->where('item.locked', true)
|
|
->where('item.content', null));
|
|
}
|
|
}
|
|
|
|
public function test_logged_in_free_user_cannot_view_creator_or_pro_prompt_content(): void
|
|
{
|
|
$creatorPrompt = AcademyPromptTemplate::query()->create([
|
|
'title' => 'Creator Prompt',
|
|
'slug' => 'free-user-creator-prompt',
|
|
'excerpt' => 'Creator preview.',
|
|
'prompt' => 'CREATOR PREMIUM PROMPT',
|
|
'negative_prompt' => 'CREATOR PREMIUM NEGATIVE',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'creator',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$proPrompt = AcademyPromptTemplate::query()->create([
|
|
'title' => 'Pro Prompt',
|
|
'slug' => 'free-user-pro-prompt',
|
|
'excerpt' => 'Pro preview.',
|
|
'prompt' => 'PRO PREMIUM PROMPT',
|
|
'negative_prompt' => 'PRO PREMIUM NEGATIVE',
|
|
'difficulty' => 'pro',
|
|
'access_level' => 'pro',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$user = User::factory()->create();
|
|
|
|
foreach ([$creatorPrompt, $proPrompt] as $prompt) {
|
|
$this->actingAs($user)
|
|
->get(route('academy.prompts.show', ['slug' => $prompt->slug]))
|
|
->assertOk()
|
|
->assertDontSee($prompt->prompt)
|
|
->assertDontSee((string) $prompt->negative_prompt)
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->where('item.locked', true)
|
|
->where('item.prompt', null)
|
|
->where('item.negative_prompt', null));
|
|
}
|
|
}
|
|
|
|
public function test_creator_user_can_view_creator_prompt_but_not_pro_prompt(): void
|
|
{
|
|
$creatorPrompt = AcademyPromptTemplate::query()->create([
|
|
'title' => 'Creator Prompt',
|
|
'slug' => 'creator-visible-prompt',
|
|
'excerpt' => 'Creator prompt.',
|
|
'prompt' => 'CREATOR ACCESS PROMPT',
|
|
'negative_prompt' => 'CREATOR ACCESS NEGATIVE',
|
|
'difficulty' => 'intermediate',
|
|
'access_level' => 'creator',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$proPrompt = AcademyPromptTemplate::query()->create([
|
|
'title' => 'Pro Prompt',
|
|
'slug' => 'creator-locked-pro-prompt',
|
|
'excerpt' => 'Pro prompt.',
|
|
'prompt' => 'PRO ONLY ACCESS PROMPT',
|
|
'negative_prompt' => 'PRO ONLY ACCESS NEGATIVE',
|
|
'difficulty' => 'pro',
|
|
'access_level' => 'pro',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$creator = User::factory()->create(['role' => 'academy_creator']);
|
|
|
|
$this->actingAs($creator)
|
|
->get(route('academy.prompts.show', ['slug' => $creatorPrompt->slug]))
|
|
->assertOk()
|
|
->assertSee('CREATOR ACCESS PROMPT')
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->where('item.locked', false)
|
|
->where('item.prompt', 'CREATOR ACCESS PROMPT'));
|
|
|
|
$this->actingAs($creator)
|
|
->get(route('academy.prompts.show', ['slug' => $proPrompt->slug]))
|
|
->assertOk()
|
|
->assertDontSee('PRO ONLY ACCESS PROMPT')
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->where('item.locked', true)
|
|
->where('item.prompt', null));
|
|
}
|
|
|
|
public function test_pro_and_admin_users_can_view_pro_prompt(): void
|
|
{
|
|
$prompt = AcademyPromptTemplate::query()->create([
|
|
'title' => 'Pro Prompt',
|
|
'slug' => 'pro-visible-prompt',
|
|
'excerpt' => 'Visible to pro and admin.',
|
|
'prompt' => 'VISIBLE TO PRO AND ADMIN',
|
|
'negative_prompt' => 'VISIBLE NEGATIVE TO PRO AND ADMIN',
|
|
'difficulty' => 'pro',
|
|
'access_level' => 'pro',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
foreach ([
|
|
User::factory()->create(['role' => 'academy_pro']),
|
|
User::factory()->create(['role' => 'admin']),
|
|
] as $user) {
|
|
$this->actingAs($user)
|
|
->get(route('academy.prompts.show', ['slug' => $prompt->slug]))
|
|
->assertOk()
|
|
->assertSee('VISIBLE TO PRO AND ADMIN')
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->where('item.locked', false)
|
|
->where('item.prompt', 'VISIBLE TO PRO AND ADMIN'));
|
|
}
|
|
}
|
|
|
|
public function test_challenge_routes_are_hidden_when_challenges_are_disabled(): void
|
|
{
|
|
config(['academy.challenges_enabled' => false]);
|
|
|
|
$this->get('/academy/challenges')->assertNotFound();
|
|
}
|
|
|
|
public function test_checkout_returns_payment_disabled_response_when_payments_are_disabled(): void
|
|
{
|
|
config(['academy.payments_enabled' => false]);
|
|
|
|
$user = User::factory()->create();
|
|
|
|
$this->actingAs($user)
|
|
->postJson(route('academy.checkout', ['plan' => 'creator-monthly']))
|
|
->assertStatus(423)
|
|
->assertJsonPath('code', 'academy_payments_disabled');
|
|
}
|
|
|
|
public function test_duplicate_prompt_save_is_idempotent(): void
|
|
{
|
|
$prompt = AcademyPromptTemplate::query()->create([
|
|
'title' => 'Free Prompt',
|
|
'slug' => 'idempotent-save-prompt',
|
|
'prompt' => 'Save me twice',
|
|
'difficulty' => 'beginner',
|
|
'access_level' => 'free',
|
|
'active' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$user = User::factory()->create();
|
|
|
|
$this->actingAs($user)->postJson(route('academy.prompts.save', ['prompt' => $prompt->id]))->assertOk();
|
|
$this->actingAs($user)->postJson(route('academy.prompts.save', ['prompt' => $prompt->id]))->assertOk();
|
|
|
|
$this->assertSame(1, DB::table('academy_saved_prompts')->where('user_id', $user->id)->where('prompt_template_id', $prompt->id)->count());
|
|
}
|
|
|
|
public function test_duplicate_challenge_submission_updates_existing_record_instead_of_creating_duplicate(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$artwork = Artwork::factory()->create([
|
|
'user_id' => $user->id,
|
|
'is_public' => true,
|
|
'is_approved' => true,
|
|
'published_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$challenge = AcademyChallenge::query()->create([
|
|
'title' => 'Duplicate Safe Challenge',
|
|
'slug' => 'duplicate-safe-challenge',
|
|
'access_level' => 'free',
|
|
'status' => 'active',
|
|
'active' => true,
|
|
'starts_at' => now()->subHour(),
|
|
'ends_at' => now()->addDay(),
|
|
]);
|
|
|
|
$payload = [
|
|
'artwork_id' => $artwork->id,
|
|
'prompt_used' => 'Prompt one',
|
|
'workflow_notes' => 'Workflow one',
|
|
'ai_tool_used' => 'ComfyUI',
|
|
'is_ai_generated' => true,
|
|
'is_ai_assisted' => true,
|
|
];
|
|
|
|
$this->actingAs($user)->post(route('academy.challenges.submit.store', ['slug' => $challenge->slug]), $payload)->assertRedirect();
|
|
|
|
$payload['workflow_notes'] = 'Workflow two';
|
|
|
|
$this->actingAs($user)->post(route('academy.challenges.submit.store', ['slug' => $challenge->slug]), $payload)->assertRedirect();
|
|
|
|
$this->assertSame(1, DB::table('academy_challenge_submissions')->where('challenge_id', $challenge->id)->where('user_id', $user->id)->where('artwork_id', $artwork->id)->count());
|
|
$this->assertDatabaseHas('academy_challenge_submissions', [
|
|
'challenge_id' => $challenge->id,
|
|
'user_id' => $user->id,
|
|
'artwork_id' => $artwork->id,
|
|
'workflow_notes' => 'Workflow two',
|
|
]);
|
|
}
|
|
}
|