Skip to content
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
2 changes: 1 addition & 1 deletion app/Http/Controllers/ResourceEditsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public function merge(ResourceEditsService $editsService, ResourceEdits $resourc
$resource->general_tags = $resourceEdits->general_tags;

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

Expand Down
2 changes: 1 addition & 1 deletion app/Http/Controllers/TagFrequencyController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public function search(string $query = "")
{
if (strlen($query) > 50)
{
return response(422)->json();
return response()->json(['message' => 'Query too long.'], 422);
}

$prefixed_tags = TagFrequency::where('tag', 'like', $query.'%')
Expand Down
2 changes: 1 addition & 1 deletion app/Models/ComputerScienceResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,6 @@ protected function generalTags(): Attribute
public function tagCounter(): array
{
$tag_collection = collect([$this->topic_tags, $this->programming_language_tags, $this->general_tags]);
return $tag_collection->flatten()->unique()->countBy()->toArray();
return $tag_collection->flatten()->countBy()->toArray();
}
}
35 changes: 18 additions & 17 deletions database/factories/ComputerScienceResourceFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ class ComputerScienceResourceFactory extends Factory
{
protected $model = ComputerScienceResource::class;

/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
protected ?array $topicTags = null;
protected ?array $programmingLanguageTags = null;
protected ?array $generalTags = null;

public function definition(): array
{
$platforms = config('computerScienceResource.platforms');
Expand All @@ -32,26 +31,28 @@ public function definition(): array
'user_id' => User::inRandomOrder()->first() ?? User::factory()->create(),
'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',
'page_url' => fake()->url(),

'platforms' => fake()->randomElements($platforms, rand(1, 3)),
'difficulty' => fake()->randomElement($difficulties),
'pricing' => fake()->randomElement($pricings),
];
}

/**
* Configure the tags
*/
public function setTags(array $topic = [], array $language = [], array $general = []): static
{
$this->topicTags = $topic;
$this->programmingLanguageTags = $language;
$this->generalTags = $general;
return $this;
}

public function configure(): Factory
{
// Define your tags here
$tags = ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', fake()->name(), fake()->name()];

// Create random tags for this resource
return $this->afterCreating(function (ComputerScienceResource $resource) use ($tags) {
$resource->topic_tags = fake()->randomElements($tags, fake()->numberBetween(3, count($tags)));
$resource->programming_language_tags = fake()->randomElements($tags);
$resource->general_tags = fake()->randomElements($tags);
return $this->afterCreating(function (ComputerScienceResource $resource) {
$fakerTags = ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', fake()->word(), fake()->word()];

$resource->topic_tags = $this->topicTags ?? fake()->randomElements($fakerTags, fake()->numberBetween(3, count($fakerTags)));
$resource->programming_language_tags = $this->programmingLanguageTags ?? fake()->randomElements($fakerTags);
$resource->general_tags = $this->generalTags ?? fake()->randomElements($fakerTags);

TagFrequencyChanged::dispatch(null, $resource->tagCounter());
});
Expand Down
2 changes: 0 additions & 2 deletions tests/Feature/ResourceEditsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,6 @@ public function test_can_post_valid_resource_edit(): void
* We run multiple merges to simulate multiple edit merges.
*/

// TODO: Handle null images
// TODO: Handle all fields and attributes
public function test_merged_edit_reflects_changes_on_original_resource(): void
{
$resource = ComputerScienceResource::factory()->create();
Expand Down
149 changes: 149 additions & 0 deletions tests/Feature/TagSearchTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<?php

namespace Tests\Feature;

use App\Events\TagFrequencyChanged;
use App\Models\ComputerScienceResource;
use App\Models\ResourceEdits;
use App\Models\TagFrequency;
use App\Models\User;
use App\Services\ResourceEditsService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Mockery;
use Tests\TestCase;
use Tests\TestResources\ComputerScienceResourceTestResource;

class TagSearchTest extends TestCase
{
use RefreshDatabase;

protected $user;

protected function setUp(): void
{
parent::setUp();
$this->user = User::factory()->create();
}


public function test_can_search_tags_by_prefix()
{
TagFrequencyChanged::dispatch(null, ['python' => 100, 'pygame' => 50, 'java' => 500]);

$response = $this->getJson(route('tags.search', ['query' => 'py']));

$response->assertStatus(200);
$response->assertJsonCount(2, 'tags'); // only 'python' and 'pygame'

$response->assertJsonFragment(['tag' => 'python', 'count' => 100]);
$response->assertJsonFragment(['tag' => 'pygame', 'count' => 50]);

// Ensure order by count descending
$tags = collect($response->json('tags'));
$this->assertEquals(['python', 'pygame'], $tags->pluck('tag')->toArray());
}

public function test_query_too_long_returns_422()
{
$query = str_repeat('a', 51); // too long

$response = $this->getJson(route('tags.search', ['query' => $query]));
$response->assertStatus(422);
}

public function test_creating_resource_updates_tag_frequency()
{
$this->actingAs($this->user);

$formData = ComputerScienceResourceTestResource::fake([
'topic_tags' => ['python', 'algorithms', 'java'],
'programming_language_tags' => ['python'],
'general_tags' => ['beginner']
]);

$response = $this->postJson(route('resources.store'), $formData);

$response->assertStatus(302); // a redirect after successful creation
$response->assertRedirect();

// Check that TagFrequency reflects counts
$this->assertDatabaseHas('tag_frequencies', ['tag' => 'python', 'count' => 2]);
$this->assertDatabaseHas('tag_frequencies', ['tag' => 'algorithms', 'count' => 1]);
$this->assertDatabaseHas('tag_frequencies', ['tag' => 'beginner', 'count' => 1]);
}

public function test_dispatching_tag_frequency_change_removes_unused_tags()
{
// Step 1: Add initial tags via dispatch
TagFrequencyChanged::dispatch(null, [
'python' => 2,
'java' => 1,
'ruby' => 1,
]);

$this->assertDatabaseHas('tag_frequencies', ['tag' => 'python', 'count' => 2]);
$this->assertDatabaseHas('tag_frequencies', ['tag' => 'java', 'count' => 1]);
$this->assertDatabaseHas('tag_frequencies', ['tag' => 'ruby', 'count' => 1]);

// Step 2: Dispatch with zero counts to simulate removal
TagFrequencyChanged::dispatch([
'python' => 2,
'java' => 1,
'ruby' => 1,
], []); // no tags used now

// Step 3: Ensure all tag frequencies are removed
$this->assertDatabaseMissing('tag_frequencies', ['tag' => 'python']);
$this->assertDatabaseMissing('tag_frequencies', ['tag' => 'java']);
$this->assertDatabaseMissing('tag_frequencies', ['tag' => 'ruby']);

// Optional: search should return empty
$response = $this->getJson(route('tags.search', ['query' => 'py']));
$response->assertStatus(200);
$this->assertEmpty($response->json('tags'));
}

public function test_merging_edit_correctly_updates_tag_frequency()
{
$resource = ComputerScienceResource::factory()
->setTags(
['algorithms', 'tag1', 'tag2'],
['c++'],
['reference']
)
->create();

$this->actingAs($this->user);

// Create an edit with new tags
$editData = ComputerScienceResourceTestResource::fake([
'topic_tags' => ['python', 'algorithms', 'tag1'], // 'algorithms' already exists, 'python' is new
'programming_language_tags' => ['python'], // replacing 'c++'
'general_tags' => ['tutorial'],
]);
$editData['edit_title'] = 'Tag Update';
$editData['edit_description'] = 'Tag change for test';

// Create the edit
$response = $this->post(route('resource_edits.store', ['computerScienceResource' => $resource->id]), $editData);
$response->assertStatus(302);

$edit = ResourceEdits::latest()->first();

// Mock approval
$this->instance(ResourceEditsService::class, Mockery::mock(ResourceEditsService::class, function ($mock) {
$mock->shouldReceive('canMergeEdits')->andReturnTrue();
}));

// Merge the edit
$mergeResponse = $this->post(route('resource_edits.merge', ['resourceEdits' => $edit->id]));
$mergeResponse->assertStatus(302);

// TagFrequency should now reflect the changes
$this->assertEquals(2, TagFrequency::where('tag', 'python')->value('count'));
$this->assertEquals(1, TagFrequency::where('tag', 'algorithms')->value('count')); // Still used once
$this->assertDatabaseMissing('tag_frequencies', ['tag' => 'c++']); // Removed
$this->assertEquals(1, TagFrequency::where('tag', 'tutorial')->value('count'));
}
}
13 changes: 9 additions & 4 deletions tests/TestResources/ComputerScienceResourceTestResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace Tests\TestResources;

use App\Events\TagFrequencyChanged;
use App\Models\ComputerScienceResource;
use Event;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

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

public static function fake(): array
public static function fake(array $overrides = []): array
{
// Create the model with disabled events
$model = ComputerScienceResource::factory()->create();

$model = Event::fakeFor(function () {
return ComputerScienceResource::factory()->create();
}, [TagFrequencyChanged::class]);

// Transform it to API form
$formData = (new self($model))->toArray(request());

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

return $formData;
// Merge and return
return array_merge($formData, $overrides);
}
}
2 changes: 1 addition & 1 deletion tests/TestResources/ResourceReviewTestResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public function toArray(Request $request): array

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