Skip to content
Closed
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
32 changes: 32 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: tests

on:
push:
branches:
- main
pull_request:

jobs:
phpunit:
name: PHPUnit (PHP ${{ matrix.php }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.2', '8.3', '8.4']
steps:
- uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: pdo, pdo_sqlite, sqlite3, curl, simplexml
coverage: none
tools: composer:v2

- name: Install dependencies
run: composer install --no-interaction --no-progress --prefer-dist

- name: Run PHPUnit
run: vendor/bin/phpunit
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
/.php-cs-fixer.cache

/composer.lock
/composer.phar
/vendor/

/.phpunit.cache/
/tests/tmp/

/yarn.lock
/node_modules/
6 changes: 5 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.48"
"friendsofphp/php-cs-fixer": "^3.48",
"phpunit/phpunit": "^11.0"
},
"scripts": {
"test": "phpunit"
}
}
15 changes: 15 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="tests/bootstrap.php"
cacheDirectory=".phpunit.cache"
colors="true"
failOnWarning="true"
failOnRisky="true"
beStrictAboutOutputDuringTests="true">
<testsuites>
<testsuite name="Unit">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>
30 changes: 30 additions & 0 deletions tests/CurlRequestTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

use PHPUnit\Framework\TestCase;

/**
* CurlRequest is the per-request token carrier. The fact that the token is
* a constructor argument (not global state) is the load-bearing property
* that lets multi-account work avoid touching the network layer — pin it.
*/
final class CurlRequestTest extends TestCase
{
public function testConstructorAssignsAllFields(): void
{
$callback = static function () {};
$request = new CurlRequest('https://api.github.com/user', 'etag-1', 'tok', $callback);

$this->assertSame('https://api.github.com/user', $request->url);
$this->assertSame('etag-1', $request->etag);
$this->assertSame('tok', $request->token);
$this->assertSame($callback, $request->callback);
}

public function testTokenMayBeNullForUnauthenticatedRequests(): void
{
$request = new CurlRequest('https://api.github.com/', null, null, static function () {});

$this->assertNull($request->token);
$this->assertNull($request->etag);
}
}
125 changes: 125 additions & 0 deletions tests/ItemRenderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

use PHPUnit\Framework\TestCase;

/**
* Characterization tests for Item::toXml. The XML structure, attribute
* ordering, escaping, and the baseUrl-prefix/enterprise-prefix rules are
* exercised against fixed inputs so that any accidental drift shows up as
* a diff in CI.
*/
final class ItemRenderTest extends TestCase
{
public function testMinimalItemProducesExpectedXml(): void
{
$item = Item::create()->title('repo');
$xml = Item::toXml([$item], false, false, 'https://github.com');

$expectedUid = md5('repo');
$expected = '<?xml version="1.0"?>'."\n".
'<items><item uid="'.$expectedUid.'" autocomplete=" repo"><icon>icon.png</icon><title>repo</title></item></items>'."\n";

$this->assertSame($expected, $xml);
}

public function testAbsolutePathArgIsPrefixedWithBaseUrl(): void
{
$item = Item::create()->title('repo')->arg('/owner/repo');
$xml = Item::toXml([$item], false, false, 'https://github.com');

$this->assertStringContainsString('arg="https://github.com/owner/repo"', $xml);
}

public function testEnterpriseFlagPrefixesNonUrlArg(): void
{
$item = Item::create()->title('search')->arg('foo bar');
$xml = Item::toXml([$item], true, false, 'https://ghe.example.com');

$this->assertStringContainsString('arg="e foo bar"', $xml);
}

public function testNonEnterpriseDoesNotPrefixArg(): void
{
$item = Item::create()->title('search')->arg('foo bar');
$xml = Item::toXml([$item], false, false, 'https://github.com');

$this->assertStringContainsString('arg="foo bar"', $xml);
}

public function testAbsoluteUrlArgIsUsedVerbatim(): void
{
$item = Item::create()->title('site')->arg('https://example.com/thing');
$xml = Item::toXml([$item], false, false, 'https://github.com');

$this->assertStringContainsString('arg="https://example.com/thing"', $xml);
}

public function testInvalidItemAppendsEllipsisAndValidNo(): void
{
$item = Item::create()->title('partial')->valid(false);
$xml = Item::toXml([$item], false, false, 'https://github.com');

$this->assertStringContainsString('valid="no"', $xml);
$this->assertStringContainsString('<title>partial&#x2026;</title>', $xml);
}

public function testInvalidItemUsesCustomSuffix(): void
{
$item = Item::create()->title('type')->valid(false, ' more');
$xml = Item::toXml([$item], false, false, 'https://github.com');

$this->assertStringContainsString('<title>type more</title>', $xml);
}

public function testSubtitleAndTitleAreHtmlEscaped(): void
{
$item = Item::create()->title('<b>bold</b>')->subtitle('a & b');
$xml = Item::toXml([$item], false, false, 'https://github.com');

$this->assertStringContainsString('<title>&lt;b&gt;bold&lt;/b&gt;</title>', $xml);
$this->assertStringContainsString('<subtitle>a &amp; b</subtitle>', $xml);
}

public function testHotkeyFlagStripsLeadingAutocompleteSpace(): void
{
$item = Item::create()->title('repo');
$xml = Item::toXml([$item], false, true, 'https://github.com');

$this->assertStringContainsString('autocomplete="repo"', $xml);
}

public function testPrefixIsIncludedInDisplayTitleButNotAutocompleteByDefault(): void
{
$item = Item::create()->prefix('gh ')->title('repo');
$xml = Item::toXml([$item], false, false, 'https://github.com');

$this->assertStringContainsString('<title>gh repo</title>', $xml);
$this->assertStringContainsString('autocomplete=" repo"', $xml);
}

public function testPrefixIncludedInAutocompleteWhenNotOnlyTitle(): void
{
$item = Item::create()->prefix('gh ', false)->title('repo');
$xml = Item::toXml([$item], false, false, 'https://github.com');

$this->assertStringContainsString('autocomplete=" gh repo"', $xml);
}

public function testMultipleItemsAreEmittedInOrder(): void
{
$items = [
Item::create()->title('first'),
Item::create()->title('second'),
Item::create()->title('third'),
];
$xml = Item::toXml($items, false, false, 'https://github.com');

$firstPos = strpos($xml, '<title>first</title>');
$secondPos = strpos($xml, '<title>second</title>');
$thirdPos = strpos($xml, '<title>third</title>');

$this->assertNotFalse($firstPos);
$this->assertLessThan($secondPos, $firstPos);
$this->assertLessThan($thirdPos, $secondPos);
}
}
43 changes: 43 additions & 0 deletions tests/WorkflowConfigTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

require_once __DIR__.'/WorkflowTestCase.php';

final class WorkflowConfigTest extends WorkflowTestCase
{
public function testSetAndGetConfigRoundTrip(): void
{
Workflow::init();

Workflow::setConfig('greeting', 'hello');

$this->assertSame('hello', Workflow::getConfig('greeting'));
}

public function testGetConfigReturnsDefaultWhenMissing(): void
{
Workflow::init();

$this->assertNull(Workflow::getConfig('missing'));
$this->assertSame('fallback', Workflow::getConfig('missing', 'fallback'));
}

public function testSetConfigOverwritesExistingValue(): void
{
Workflow::init();

Workflow::setConfig('k', 'v1');
Workflow::setConfig('k', 'v2');

$this->assertSame('v2', Workflow::getConfig('k'));
}

public function testRemoveConfigDeletesKey(): void
{
Workflow::init();

Workflow::setConfig('k', 'v');
Workflow::removeConfig('k');

$this->assertNull(Workflow::getConfig('k'));
}
}
58 changes: 58 additions & 0 deletions tests/WorkflowEnterpriseUrlTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

require_once __DIR__.'/WorkflowTestCase.php';

/**
* Pins the URL derivation performed by Workflow::init when the enterprise
* flag is set: baseUrl is read from the enterprise_url config key, and the
* API/gist URLs are derived from it. When no config value is present the
* derived URLs become null — this is the current (pre-multi-account) shape
* and callers depend on it.
*/
final class WorkflowEnterpriseUrlTest extends WorkflowTestCase
{
public function testDefaultGithubUrls(): void
{
Workflow::init();

$this->assertSame('https://github.com', Workflow::getBaseUrl());
$this->assertSame('https://api.github.com', Workflow::getApiUrl());
$this->assertSame('https://gist.github.com', Workflow::getGistUrl());
}

public function testEnterpriseUrlsDerivedFromConfig(): void
{
Workflow::init();
Workflow::setConfig('enterprise_url', 'https://ghe.example.com');

agw_test_reset_workflow();
Workflow::init(true);

$this->assertSame('https://ghe.example.com', Workflow::getBaseUrl());
$this->assertSame('https://ghe.example.com/api/v3', Workflow::getApiUrl());
$this->assertSame('https://ghe.example.com/gist', Workflow::getGistUrl());
}

public function testEnterpriseUrlsAreNullWhenConfigMissing(): void
{
Workflow::init(true);

$this->assertNull(Workflow::getBaseUrl());
$this->assertNull(Workflow::getApiUrl());
$this->assertNull(Workflow::getGistUrl());
}

public function testGetApiUrlAppendsPathAndPerPage(): void
{
Workflow::init();

$this->assertSame(
'https://api.github.com/user/repos?per_page=100',
Workflow::getApiUrl('/user/repos')
);
$this->assertSame(
'https://api.github.com/search/repositories?q=foo&per_page=100',
Workflow::getApiUrl('/search/repositories?q=foo')
);
}
}
Loading
Loading