Skip to content

Commit d2f7b48

Browse files
committed
Unit tests for the tags
1 parent a9d5329 commit d2f7b48

8 files changed

Lines changed: 180 additions & 27 deletions

File tree

app/Http/Controllers/ResourceEditsController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public function merge(ResourceEditsService $editsService, ResourceEdits $resourc
111111
$resource->general_tags = $resourceEdits->general_tags;
112112

113113
// Get the new tag counter
114-
$new_tags = collect([$resourceEdits->topic_tags, $resourceEdits->programming_language_tags, $resourceEdits->general_tags])->flatten()->unique()->countBy()->toArray();
114+
$new_tags = collect([$resourceEdits->topic_tags, $resourceEdits->programming_language_tags, $resourceEdits->general_tags])->flatten()->countBy()->toArray();
115115
// Change tag frequency
116116
TagFrequencyChanged::dispatch($old_tag_counter, $new_tags);
117117

app/Http/Controllers/TagFrequencyController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public function search(string $query = "")
1111
{
1212
if (strlen($query) > 50)
1313
{
14-
return response(422)->json();
14+
return response()->json(['message' => 'Query too long.'], 422);
1515
}
1616

1717
$prefixed_tags = TagFrequency::where('tag', 'like', $query.'%')

app/Models/ComputerScienceResource.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,6 @@ protected function generalTags(): Attribute
108108
public function tagCounter(): array
109109
{
110110
$tag_collection = collect([$this->topic_tags, $this->programming_language_tags, $this->general_tags]);
111-
return $tag_collection->flatten()->unique()->countBy()->toArray();
111+
return $tag_collection->flatten()->countBy()->toArray();
112112
}
113113
}

database/factories/ComputerScienceResourceFactory.php

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@ class ComputerScienceResourceFactory extends Factory
1515
{
1616
protected $model = ComputerScienceResource::class;
1717

18-
/**
19-
* Define the model's default state.
20-
*
21-
* @return array<string, mixed>
22-
*/
18+
protected ?array $topicTags = null;
19+
protected ?array $programmingLanguageTags = null;
20+
protected ?array $generalTags = null;
21+
2322
public function definition(): array
2423
{
2524
$platforms = config('computerScienceResource.platforms');
@@ -32,26 +31,28 @@ public function definition(): array
3231
'user_id' => User::inRandomOrder()->first() ?? User::factory()->create(),
3332
'image_url' => 'https://cdn.iconscout.com/icon/free/png-256/free-leetcode-logo-icon-download-in-svg-png-gif-file-formats--technology-social-media-company-vol-1-pack-logos-icons-3030025.png',
3433
'page_url' => fake()->url(),
35-
3634
'platforms' => fake()->randomElements($platforms, rand(1, 3)),
3735
'difficulty' => fake()->randomElement($difficulties),
3836
'pricing' => fake()->randomElement($pricings),
3937
];
4038
}
4139

42-
/**
43-
* Configure the tags
44-
*/
40+
public function setTags(array $topic = [], array $language = [], array $general = []): static
41+
{
42+
$this->topicTags = $topic;
43+
$this->programmingLanguageTags = $language;
44+
$this->generalTags = $general;
45+
return $this;
46+
}
47+
4548
public function configure(): Factory
4649
{
47-
// Define your tags here
48-
$tags = ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', fake()->name(), fake()->name()];
49-
50-
// Create random tags for this resource
51-
return $this->afterCreating(function (ComputerScienceResource $resource) use ($tags) {
52-
$resource->topic_tags = fake()->randomElements($tags, fake()->numberBetween(3, count($tags)));
53-
$resource->programming_language_tags = fake()->randomElements($tags);
54-
$resource->general_tags = fake()->randomElements($tags);
50+
return $this->afterCreating(function (ComputerScienceResource $resource) {
51+
$fakerTags = ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', fake()->word(), fake()->word()];
52+
53+
$resource->topic_tags = $this->topicTags ?? fake()->randomElements($fakerTags, fake()->numberBetween(3, count($fakerTags)));
54+
$resource->programming_language_tags = $this->programmingLanguageTags ?? fake()->randomElements($fakerTags);
55+
$resource->general_tags = $this->generalTags ?? fake()->randomElements($fakerTags);
5556

5657
TagFrequencyChanged::dispatch(null, $resource->tagCounter());
5758
});

tests/Feature/ResourceEditsTest.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,6 @@ public function test_can_post_valid_resource_edit(): void
153153
* We run multiple merges to simulate multiple edit merges.
154154
*/
155155

156-
// TODO: Handle null images
157-
// TODO: Handle all fields and attributes
158156
public function test_merged_edit_reflects_changes_on_original_resource(): void
159157
{
160158
$resource = ComputerScienceResource::factory()->create();

tests/Feature/TagSearchTest.php

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
<?php
2+
3+
namespace Tests\Feature;
4+
5+
use App\Events\TagFrequencyChanged;
6+
use App\Models\ComputerScienceResource;
7+
use App\Models\ResourceEdits;
8+
use App\Models\TagFrequency;
9+
use App\Models\User;
10+
use App\Services\ResourceEditsService;
11+
use Illuminate\Foundation\Testing\RefreshDatabase;
12+
use Illuminate\Foundation\Testing\WithFaker;
13+
use Mockery;
14+
use Tests\TestCase;
15+
use Tests\TestResources\ComputerScienceResourceTestResource;
16+
17+
class TagSearchTest extends TestCase
18+
{
19+
use RefreshDatabase;
20+
21+
protected $user;
22+
23+
protected function setUp(): void
24+
{
25+
parent::setUp();
26+
$this->user = User::factory()->create();
27+
}
28+
29+
30+
public function test_can_search_tags_by_prefix()
31+
{
32+
TagFrequencyChanged::dispatch(null, ['python' => 100, 'pygame' => 50, 'java' => 500]);
33+
34+
$response = $this->getJson(route('tags.search', ['query' => 'py']));
35+
36+
$response->assertStatus(200);
37+
$response->assertJsonCount(2, 'tags'); // only 'python' and 'pygame'
38+
39+
$response->assertJsonFragment(['tag' => 'python', 'count' => 100]);
40+
$response->assertJsonFragment(['tag' => 'pygame', 'count' => 50]);
41+
42+
// Ensure order by count descending
43+
$tags = collect($response->json('tags'));
44+
$this->assertEquals(['python', 'pygame'], $tags->pluck('tag')->toArray());
45+
}
46+
47+
public function test_query_too_long_returns_422()
48+
{
49+
$query = str_repeat('a', 51); // too long
50+
51+
$response = $this->getJson(route('tags.search', ['query' => $query]));
52+
$response->assertStatus(422);
53+
}
54+
55+
public function test_creating_resource_updates_tag_frequency()
56+
{
57+
$this->actingAs($this->user);
58+
59+
$formData = ComputerScienceResourceTestResource::fake([
60+
'topic_tags' => ['python', 'algorithms', 'java'],
61+
'programming_language_tags' => ['python'],
62+
'general_tags' => ['beginner']
63+
]);
64+
65+
$response = $this->postJson(route('resources.store'), $formData);
66+
67+
$response->assertStatus(302); // a redirect after successful creation
68+
$response->assertRedirect();
69+
70+
// Check that TagFrequency reflects counts
71+
$this->assertDatabaseHas('tag_frequencies', ['tag' => 'python', 'count' => 2]);
72+
$this->assertDatabaseHas('tag_frequencies', ['tag' => 'algorithms', 'count' => 1]);
73+
$this->assertDatabaseHas('tag_frequencies', ['tag' => 'beginner', 'count' => 1]);
74+
}
75+
76+
public function test_dispatching_tag_frequency_change_removes_unused_tags()
77+
{
78+
// Step 1: Add initial tags via dispatch
79+
TagFrequencyChanged::dispatch(null, [
80+
'python' => 2,
81+
'java' => 1,
82+
'ruby' => 1,
83+
]);
84+
85+
$this->assertDatabaseHas('tag_frequencies', ['tag' => 'python', 'count' => 2]);
86+
$this->assertDatabaseHas('tag_frequencies', ['tag' => 'java', 'count' => 1]);
87+
$this->assertDatabaseHas('tag_frequencies', ['tag' => 'ruby', 'count' => 1]);
88+
89+
// Step 2: Dispatch with zero counts to simulate removal
90+
TagFrequencyChanged::dispatch([
91+
'python' => 2,
92+
'java' => 1,
93+
'ruby' => 1,
94+
], []); // no tags used now
95+
96+
// Step 3: Ensure all tag frequencies are removed
97+
$this->assertDatabaseMissing('tag_frequencies', ['tag' => 'python']);
98+
$this->assertDatabaseMissing('tag_frequencies', ['tag' => 'java']);
99+
$this->assertDatabaseMissing('tag_frequencies', ['tag' => 'ruby']);
100+
101+
// Optional: search should return empty
102+
$response = $this->getJson(route('tags.search', ['query' => 'py']));
103+
$response->assertStatus(200);
104+
$this->assertEmpty($response->json('tags'));
105+
}
106+
107+
public function test_merging_edit_correctly_updates_tag_frequency()
108+
{
109+
$resource = ComputerScienceResource::factory()
110+
->setTags(
111+
['algorithms', 'tag1', 'tag2'],
112+
['c++'],
113+
['reference']
114+
)
115+
->create();
116+
117+
$this->actingAs($this->user);
118+
119+
// Create an edit with new tags
120+
$editData = ComputerScienceResourceTestResource::fake([
121+
'topic_tags' => ['python', 'algorithms', 'tag1'], // 'algorithms' already exists, 'python' is new
122+
'programming_language_tags' => ['python'], // replacing 'c++'
123+
'general_tags' => ['tutorial'],
124+
]);
125+
$editData['edit_title'] = 'Tag Update';
126+
$editData['edit_description'] = 'Tag change for test';
127+
128+
// Create the edit
129+
$response = $this->post(route('resource_edits.store', ['computerScienceResource' => $resource->id]), $editData);
130+
$response->assertStatus(302);
131+
132+
$edit = ResourceEdits::latest()->first();
133+
134+
// Mock approval
135+
$this->instance(ResourceEditsService::class, Mockery::mock(ResourceEditsService::class, function ($mock) {
136+
$mock->shouldReceive('canMergeEdits')->andReturnTrue();
137+
}));
138+
139+
// Merge the edit
140+
$mergeResponse = $this->post(route('resource_edits.merge', ['resourceEdits' => $edit->id]));
141+
$mergeResponse->assertStatus(302);
142+
143+
// TagFrequency should now reflect the changes
144+
$this->assertEquals(2, TagFrequency::where('tag', 'python')->value('count'));
145+
$this->assertEquals(1, TagFrequency::where('tag', 'algorithms')->value('count')); // Still used once
146+
$this->assertDatabaseMissing('tag_frequencies', ['tag' => 'c++']); // Removed
147+
$this->assertEquals(1, TagFrequency::where('tag', 'tutorial')->value('count'));
148+
}
149+
}

tests/TestResources/ComputerScienceResourceTestResource.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace Tests\TestResources;
44

5+
use App\Events\TagFrequencyChanged;
56
use App\Models\ComputerScienceResource;
7+
use Event;
68
use Illuminate\Http\Request;
79
use Illuminate\Http\Resources\Json\JsonResource;
810

@@ -24,17 +26,20 @@ public function toArray(Request $request): array
2426
];
2527
}
2628

27-
public static function fake(): array
29+
public static function fake(array $overrides = []): array
2830
{
2931
// Create the model with disabled events
30-
$model = ComputerScienceResource::factory()->create();
31-
32+
$model = Event::fakeFor(function () {
33+
return ComputerScienceResource::factory()->create();
34+
}, [TagFrequencyChanged::class]);
35+
3236
// Transform it to API form
3337
$formData = (new self($model))->toArray(request());
3438

3539
// Delete after getting the array to avoid polluting the DB
3640
$model->delete();
3741

38-
return $formData;
42+
// Merge and return
43+
return array_merge($formData, $overrides);
3944
}
4045
}

tests/TestResources/ResourceReviewTestResource.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public function toArray(Request $request): array
2828

2929
public static function fake(): array
3030
{
31-
// Fake all events except the ones you still want to fire
31+
// Fake certain events
3232
$model = Event::fakeFor(function () {
3333
return ResourceReview::factory()->create();
3434
}, [ResourceReviewProcessed::class]);

0 commit comments

Comments
 (0)