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
121 changes: 97 additions & 24 deletions src/Services/IssueQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
namespace ConduitUI\Issue\Services;

use ConduitUi\GitHubConnector\Connector;
use ConduitUI\Issue\Contracts\IssueQueryInterface;
use ConduitUI\Issue\Data\Issue;
use ConduitUI\Issue\Requests\Issues\ListIssuesRequest;
use DateTime;
use DateTimeInterface;
use Illuminate\Support\Collection;

class IssueQuery
class IssueQuery implements IssueQueryInterface
{
/**
* @var array<string, mixed>
Expand All @@ -24,61 +26,91 @@ public function __construct(
) {}

/**
* Filter issues by state.
* Filter issues by state (interface method).
*/
public function whereState(string $state): self
public function state(string $state): self
{
$this->filters['state'] = $state;

return $this;
}

/**
* Filter issues by state (alias for backward compatibility).
*/
public function whereState(string $state): self
{
return $this->state($state);
}

/**
* Filter for open issues.
*/
public function whereOpen(): self
{
return $this->whereState('open');
return $this->state('open');
}

/**
* Filter for closed issues.
*/
public function whereClosed(): self
{
return $this->whereState('closed');
return $this->state('closed');
}

/**
* Filter by a single label.
* Filter issues by labels (interface method).
*
* @param array<string>|string $labels
*/
public function whereLabel(string $label): self
public function labels(array|string $labels): self
{
$this->filters['labels'] = $label;
if (is_array($labels)) {
$this->filters['labels'] = implode(',', $labels);
} else {
$this->filters['labels'] = $labels;
}

return $this;
}

/**
* Filter by multiple labels (comma-separated).
* Filter by a single label (alias for backward compatibility).
*/
public function whereLabels(array $labels): self
public function whereLabel(string $label): self
{
$this->filters['labels'] = implode(',', $labels);
return $this->labels($label);
}

return $this;
/**
* Filter by multiple labels (alias for backward compatibility).
*
* @param array<string> $labels
*/
public function whereLabels(array $labels): self
{
return $this->labels($labels);
}

/**
* Filter by assignee.
* Filter issues by assignee username (interface method).
*/
public function assignedTo(string $username): self
public function assignee(string $username): self
{
$this->filters['assignee'] = $username;

return $this;
}

/**
* Filter by assignee (alias for backward compatibility).
*/
public function assignedTo(string $username): self
{
return $this->assignee($username);
}

/**
* Filter for unassigned issues.
*/
Expand All @@ -90,35 +122,59 @@ public function whereUnassigned(): self
}

/**
* Filter by creator.
* Filter issues by creator username (interface method).
*/
public function createdBy(string $username): self
public function creator(string $username): self
{
$this->filters['creator'] = $username;

return $this;
}

/**
* Filter by mentioned user.
* Filter by creator (alias for backward compatibility).
*/
public function mentioning(string $username): self
public function createdBy(string $username): self
{
return $this->creator($username);
}

/**
* Filter issues mentioning a specific user (interface method).
*/
public function mentioned(string $username): self
{
$this->filters['mentioned'] = $username;

return $this;
}

/**
* Filter by created after date.
* Filter by mentioned user (alias for backward compatibility).
*/
public function createdAfter(string|DateTime $date): self
public function mentioning(string $username): self
{
return $this->mentioned($username);
}

/**
* Filter issues updated since a given date (interface method).
*/
public function since(string|DateTimeInterface $date): self
{
$this->filters['since'] = $this->formatDate($date);

return $this;
}

/**
* Filter by created after date (alias for backward compatibility).
*/
public function createdAfter(string|DateTime $date): self
{
return $this->since($date);
}

/**
* Filter by updated before date (using updated_at for staleness check).
*/
Expand All @@ -142,16 +198,33 @@ public function older(int $days): self
}

/**
* Sort by field and direction.
* Sort issues by created, updated, or comments (interface method).
*/
public function orderBy(string $field, string $direction = 'asc'): self
public function sort(string $field): self
{
$this->filters['sort'] = $field;

return $this;
}

/**
* Set sort direction (asc or desc) (interface method).
*/
public function direction(string $direction): self
{
$this->filters['direction'] = $direction;

return $this;
}

/**
* Sort by field and direction (convenience method).
*/
public function orderBy(string $field, string $direction = 'asc'): self
{
return $this->sort($field)->direction($direction);
}

/**
* Sort by created date.
*/
Expand Down Expand Up @@ -252,9 +325,9 @@ public function exists(): bool
/**
* Format date to ISO 8601 format.
*/
protected function formatDate(string|DateTime $date): string
protected function formatDate(string|DateTimeInterface $date): string
{
if ($date instanceof DateTime) {
if ($date instanceof DateTimeInterface) {
return $date->format('c');
}

Expand Down
119 changes: 119 additions & 0 deletions tests/Unit/Services/IssueQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -293,4 +293,123 @@
expect($this->query->exists())->toBeFalse();
});
});

describe('Interface Method Compliance', function () {
it('supports state() method from interface', function () {
$this->mockClient->addResponse(MockResponse::make([
fullIssueResponse(['number' => 1, 'state' => 'open']),
]));

$issues = $this->query->state('open')->get();

expect($issues)->toHaveCount(1)
->and($issues->first()->state)->toBe('open');
});

it('supports labels() with array from interface', function () {
$this->mockClient->addResponse(MockResponse::make([
fullIssueResponse(['number' => 1, 'labels' => [
['id' => 1, 'name' => 'bug', 'color' => 'ff0000', 'description' => 'Bug'],
['id' => 2, 'name' => 'urgent', 'color' => '00ff00', 'description' => 'Urgent'],
]]),
]));

$issues = $this->query->labels(['bug', 'urgent'])->get();

expect($issues)->toHaveCount(1);
});

it('supports labels() with string from interface', function () {
$this->mockClient->addResponse(MockResponse::make([
fullIssueResponse(['number' => 1, 'labels' => [
['id' => 1, 'name' => 'bug', 'color' => 'ff0000', 'description' => 'Bug'],
]]),
]));

$issues = $this->query->labels('bug')->get();

expect($issues)->toHaveCount(1);
});

it('supports assignee() method from interface', function () {
$this->mockClient->addResponse(MockResponse::make([
fullIssueResponse(['number' => 1, 'assignee' => ['id' => 1, 'login' => 'johndoe', 'avatar_url' => 'https://example.com/avatar.png', 'html_url' => 'https://github.com/johndoe', 'type' => 'User']]),
]));

$issues = $this->query->assignee('johndoe')->get();

expect($issues)->toHaveCount(1);
});

it('supports creator() method from interface', function () {
$this->mockClient->addResponse(MockResponse::make([
fullIssueResponse(['number' => 1, 'user' => ['id' => 1, 'login' => 'janedoe', 'avatar_url' => 'https://example.com/avatar.png', 'html_url' => 'https://github.com/janedoe', 'type' => 'User']]),
]));

$issues = $this->query->creator('janedoe')->get();

expect($issues)->toHaveCount(1);
});

it('supports mentioned() method from interface', function () {
$this->mockClient->addResponse(MockResponse::make([
fullIssueResponse(['number' => 1]),
]));

$issues = $this->query->mentioned('johndoe')->get();

expect($issues)->toHaveCount(1);
});

it('supports since() method from interface', function () {
$this->mockClient->addResponse(MockResponse::make([
fullIssueResponse(['number' => 1, 'created_at' => '2024-01-15T00:00:00Z']),
]));

$issues = $this->query->since('2024-01-01')->get();

expect($issues)->toHaveCount(1);
});

it('supports sort() method from interface', function () {
$this->mockClient->addResponse(MockResponse::make([
fullIssueResponse(['number' => 1, 'created_at' => '2024-01-01T00:00:00Z']),
fullIssueResponse(['number' => 2, 'created_at' => '2024-01-02T00:00:00Z']),
]));

$issues = $this->query->sort('created')->get();

expect($issues)->toHaveCount(2);
});

it('supports direction() method from interface', function () {
$this->mockClient->addResponse(MockResponse::make([
fullIssueResponse(['number' => 1]),
fullIssueResponse(['number' => 2]),
]));

$issues = $this->query->sort('created')->direction('desc')->get();

expect($issues)->toHaveCount(2);
});

it('supports fluent chaining with interface methods', function () {
$this->mockClient->addResponse(MockResponse::make([
fullIssueResponse(['number' => 1, 'state' => 'open', 'labels' => [['id' => 1, 'name' => 'bug', 'color' => 'ff0000', 'description' => 'Bug']]]),
]));

$issues = $this->query
->state('open')
->labels(['bug'])
->assignee('johndoe')
->creator('janedoe')
->sort('created')
->direction('desc')
->perPage(10)
->page(1)
->get();

expect($issues)->toHaveCount(1);
});
});
});
Loading