Skip to content

Commit 5c883f7

Browse files
authored
Comprehensive test suite improvements and CI fix (#183)
* Fix test infrastructure from previous session - Suppress PDO::MYSQL_ATTR_SSL_CA deprecation on PHP 8.5 - Enable Unit test suite in phpunit.xml (was commented out) - Fix ReactionFactory to use User::factory() instead of hardcoded IDs * Fix existing test issues - Add signIn() helper using actingAs() for faster auth in tests - Remove duplicate signInAndReturnToken() from LessonTest - Fix CodandoWebhookTest to not depend on seeded data - Un-skip WorkshopTest shape test (was using wrong field names) - Add missing @test annotation on workshop soon list test - Refactor LessonTest to use signIn() instead of real HTTP login * Add factory states for deterministic tests - ChallengeFactory: default to draft, add published/draft/soon states - WorkshopFactory: default to draft, add published/draft/soon states - UserFactory: add pro() and admin() states - SubscriptionFactory: add active/expired/canceled states - LessonFactory: add forWorkshop/forChallenge states * Extract reusable test traits - Create BuildsPagarmePayloads trait from PagarmeWebhookFullTest - Create CreatesSubscription trait for subscription test setup - Refactor PagarmeWebhookFullTest to use shared trait * Add high-priority feature tests - DashboardTest: auth, name/password/linkedin/avatar/settings changes - CertificateTest: create, duplicate prevention, show by id/slug - TrackTest: list published/soon, exclude draft, show by slug - RankingTest: list, monthly filter, admin exclusion - LeadsTest: create, validation, duplicate prevention * Add medium-priority feature tests - InstructorTest: list, show by slug, workshops relationship - CalendarTest: events endpoint, date range filtering - TrackableTest: auth, mark complete, toggle completion * Add unit tests for models, mail, and notifications - UserTest: subscribeToPlan, upgrade/downgrade pro, admin check - SubscriptionTest: translated status/payment, changeStatus - CertificateTest: validateCertifiable method - MailableTest: all 7 mail classes (subjects, payment method views) - NotificationTest: certificate published, comment notifications * Add listener, observer, and service tests - AwardPointsTest: all 5 event types (complete, join, fork, reaction) - CertificateObserverTest: status change dispatches event - DiscordServiceTest: send message, embeds, test channel routing * Fix CI pipeline - Upgrade MySQL 5.7 to 8.0 - Add missing migration step before running tests - Fix APP_ENV to testing (was ci, broke Discord service mock) - Add BCRYPT_ROUNDS=4 for faster CI - Fix deprecated MAIL_DRIVER/QUEUE_DRIVER to MAIL_MAILER/QUEUE_CONNECTION - Add TELESCOPE_ENABLED=false - Update actions/checkout to v4, shivammathur/setup-php to v2 - Remove deprecated --no-suggest Composer flag - Split tests and deploy into separate jobs * Add MocksExternalServices trait Reusable trait for mocking GitHub, Discord, and other external HTTP calls in tests.
1 parent 22722e9 commit 5c883f7

40 files changed

Lines changed: 2711 additions & 202 deletions

.env.github

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
APP_ENV=ci
1+
APP_ENV=testing
22
APP_KEY=
33
APP_DEBUG=true
44
APP_URL=https://localhost
5+
APP_FRONTEND_URL=https://codante.io
56

67
LOG_CHANNEL=stack
78

@@ -12,9 +13,11 @@ DB_DATABASE=codante_test
1213
DB_USERNAME=user
1314
DB_PASSWORD=password
1415

16+
BCRYPT_ROUNDS=4
17+
1518
SESSION_DRIVER=array
1619
CACHE_DRIVER=array
20+
QUEUE_CONNECTION=sync
21+
MAIL_MAILER=array
1722

18-
QUEUE_DRIVER=sync
19-
20-
MAIL_DRIVER=log
23+
TELESCOPE_ENABLED=false

.github/workflows/main.yml

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ on:
55
branches: [main]
66

77
jobs:
8-
deploy:
9-
name: deploy
8+
tests:
9+
name: Tests
1010
runs-on: ubuntu-latest
1111

1212
services:
1313
mysql:
14-
image: mysql:5.7
14+
image: mysql:8.0
1515
env:
1616
MYSQL_DATABASE: codante_test
1717
MYSQL_USER: user
@@ -22,36 +22,57 @@ jobs:
2222
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
2323

2424
steps:
25-
- uses: actions/checkout@v3
25+
- uses: actions/checkout@v4
2626

2727
- name: Setup PHP
28-
uses: shivammathur/setup-php@master
28+
uses: shivammathur/setup-php@v2
2929
with:
3030
php-version: 8.3
3131
extensions: mbstring, bcmath, zip
3232
tools: composer:v2
33+
coverage: none
3334

3435
- name: Composer install
35-
run: composer install --prefer-dist --no-interaction --no-suggest
36-
37-
# - name: Check code style (Laravel Pint)
38-
# run: ./vendor/bin/pint --test
36+
run: composer install --prefer-dist --no-interaction
3937

4038
- name: Boot Laravel application
4139
run: |
4240
cp .env.github .env
4341
php artisan key:generate
4442
php artisan --version
4543
44+
- name: Run migrations
45+
run: php artisan migrate --force
46+
env:
47+
DB_PORT: ${{ job.services.mysql.ports[3306] }}
48+
4649
- name: Execute tests (Unit and Feature tests) via PHPUnit
4750
run: vendor/bin/phpunit
4851
env:
4952
DB_PORT: ${{ job.services.mysql.ports[3306] }}
5053

54+
deploy:
55+
name: Deploy
56+
runs-on: ubuntu-latest
57+
needs: tests
58+
59+
if: github.ref == 'refs/heads/main'
60+
61+
steps:
62+
- uses: actions/checkout@v4
63+
64+
- name: Setup PHP
65+
uses: shivammathur/setup-php@v2
66+
with:
67+
php-version: 8.3
68+
tools: composer:v2
69+
coverage: none
70+
71+
- name: Composer install
72+
run: composer install --prefer-dist --no-interaction
73+
5174
- name: Setup Deployer
5275
uses: deployphp/action@master
5376
with:
5477
private-key: ${{ secrets.SSH_PRIVATE_KEY }}
55-
56-
## known-hosts: ${{ secrets.SSH_KNOWN_HOSTS }}
5778
dep: deploy -v

config/database.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
'strict' => true,
6060
'engine' => null,
6161
'options' => extension_loaded('pdo_mysql') ? array_filter([
62-
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
62+
@constant('PDO::MYSQL_ATTR_SSL_CA') ?: 1009 => env('MYSQL_ATTR_SSL_CA'),
6363
]) : [],
6464
],
6565

database/factories/ChallengeFactory.php

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,27 @@ public function definition(): array
2323
'image_url' => fake()->imageUrl(640, 480, 'Avatar', true),
2424
'video_url' => 'https://player.vimeo.com/video/22331996',
2525
'slug' => fake()->slug(4),
26-
'status' => fake()->randomElement([
27-
'draft',
28-
'published',
29-
'soon',
30-
'archived',
31-
]),
26+
'status' => 'draft',
3227
'difficulty' => fake()->numberBetween(1, 3),
3328
'duration_in_minutes' => fake()->numberBetween(60, 300),
3429
'repository_name' => fake()->url(),
3530
'position' => fake()->numberBetween(1, 100),
36-
'featured' => fake()->randomElement(['landing', null, 'new']),
31+
'featured' => null,
3732
];
3833
}
34+
35+
public function published(): static
36+
{
37+
return $this->state(fn () => ['status' => 'published']);
38+
}
39+
40+
public function draft(): static
41+
{
42+
return $this->state(fn () => ['status' => 'draft']);
43+
}
44+
45+
public function soon(): static
46+
{
47+
return $this->state(fn () => ['status' => 'soon']);
48+
}
3949
}

database/factories/LessonFactory.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Database\Factories;
44

5+
use App\Models\Challenge;
6+
use App\Models\Workshop;
57
use Illuminate\Database\Eloquent\Factories\Factory;
68

79
/**
@@ -23,6 +25,24 @@ public function definition(): array
2325
'duration_in_seconds' => fake()->numberBetween(60, 3600),
2426
'video_url' => 'https://player.vimeo.com/video/112836958',
2527
'slug' => fake()->slug(4),
28+
'lessonable_id' => Challenge::factory(),
29+
'lessonable_type' => Challenge::class,
2630
];
2731
}
32+
33+
public function forWorkshop(?Workshop $workshop = null): static
34+
{
35+
return $this->state(fn () => [
36+
'lessonable_id' => $workshop?->id ?? Workshop::factory(),
37+
'lessonable_type' => Workshop::class,
38+
]);
39+
}
40+
41+
public function forChallenge(?Challenge $challenge = null): static
42+
{
43+
return $this->state(fn () => [
44+
'lessonable_id' => $challenge?->id ?? Challenge::factory(),
45+
'lessonable_type' => Challenge::class,
46+
]);
47+
}
2848
}

database/factories/ReactionFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public function definition(): array
1818
{
1919
return [
2020
'reaction' => $this->faker->randomElement(['like', 'fire', 'rocket', 'exploding-head']),
21-
'user_id' => $this->faker->numberBetween(1, 10),
21+
'user_id' => \App\Models\User::factory(),
2222
];
2323
}
2424
}

database/factories/SubscriptionFactory.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,29 @@ public function definition()
2121
'starts_at' => Carbon::now(), // Default start date
2222
'ends_at' => Carbon::now()->addMonth(),
2323
'acquisition_type' => 'paid',
24-
// ...other fields...
2524
];
2625
}
26+
27+
public function active(): static
28+
{
29+
return $this->state(fn () => [
30+
'status' => 'active',
31+
'starts_at' => Carbon::now(),
32+
'ends_at' => Carbon::now()->addMonth(),
33+
]);
34+
}
35+
36+
public function expired(): static
37+
{
38+
return $this->state(fn () => [
39+
'status' => 'active',
40+
'starts_at' => Carbon::now()->subMonths(2),
41+
'ends_at' => Carbon::now()->subDay(),
42+
]);
43+
}
44+
45+
public function canceled(): static
46+
{
47+
return $this->state(fn () => ['status' => 'canceled']);
48+
}
2749
}

database/factories/UserFactory.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,14 @@ public function unverified(): static
4141
]
4242
);
4343
}
44+
45+
public function pro(): static
46+
{
47+
return $this->state(fn () => ['is_pro' => true]);
48+
}
49+
50+
public function admin(): static
51+
{
52+
return $this->state(fn () => ['is_admin' => true]);
53+
}
4454
}

database/factories/WorkshopFactory.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,28 @@ public function definition(): array
2424
'image_url' => fake()->imageUrl(640, 480, 'Avatar', true),
2525
'video_url' => 'https://player.vimeo.com/video/4812350',
2626
'slug' => fake()->slug(4),
27-
'status' => fake()->randomElement(['draft', 'published', 'soon', 'archived']),
27+
'status' => 'draft',
2828
'is_standalone' => fake()->boolean(),
2929
'difficulty' => fake()->numberBetween(1, 3),
3030
'duration_in_minutes' => fake()->numberBetween(60, 300),
3131
'instructor_id' => Instructor::factory(),
32-
'featured' => fake()->randomElement(['landing', null, 'new']),
32+
'featured' => null,
3333
'published_at' => fake()->dateTimeBetween('-1 year', 'now'),
3434
];
3535
}
36+
37+
public function published(): static
38+
{
39+
return $this->state(fn () => ['status' => 'published']);
40+
}
41+
42+
public function draft(): static
43+
{
44+
return $this->state(fn () => ['status' => 'draft']);
45+
}
46+
47+
public function soon(): static
48+
{
49+
return $this->state(fn () => ['status' => 'soon']);
50+
}
3651
}

phpunit.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true">
33
<testsuites>
4-
<!-- <testsuite name="Unit">
4+
<testsuite name="Unit">
55
<directory suffix="Test.php">./tests/Unit</directory>
6-
</testsuite> -->
6+
</testsuite>
77
<testsuite name="Feature">
88
<directory suffix="Test.php">./tests/Feature</directory>
99
</testsuite>

0 commit comments

Comments
 (0)