init
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Admin;
|
||||
use App\Models\Channel;
|
||||
use App\Services\ChannelProjectService;
|
||||
use Illuminate\Events\Dispatcher;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Mockery;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use RuntimeException;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ChannelProjectServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private const CHANNEL_ID = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
config([
|
||||
'coruna.build_service.url' => 'https://builder.test',
|
||||
'coruna.build_service.token' => 'secret-token',
|
||||
'coruna.build_service.connect_timeout' => 2,
|
||||
'coruna.build_service.timeout' => 30,
|
||||
'coruna.deployment_domains' => ['deploy.test'],
|
||||
'coruna.reporting_domains' => ['report.test'],
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_builds_with_authenticated_separate_domain_lists(): void
|
||||
{
|
||||
Http::fake(['builder.test/*' => Http::response(['ok' => true])]);
|
||||
|
||||
app(ChannelProjectService::class)->generate(self::CHANNEL_ID);
|
||||
|
||||
Http::assertSent(fn ($request) => $request->method() === 'POST'
|
||||
&& $request->url() === 'https://builder.test/v1/channels/'.self::CHANNEL_ID.'/build'
|
||||
&& $request->hasHeader('Authorization', 'Bearer secret-token')
|
||||
&& $request->data() === [
|
||||
'deployment_domains' => ['deploy.test'],
|
||||
'reporting_domains' => ['report.test'],
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_prefers_channel_domains_for_deployment_and_env_for_reporting(): void
|
||||
{
|
||||
Http::fake(['builder.test/*' => Http::response(['ok' => true])]);
|
||||
|
||||
app(ChannelProjectService::class)->generate(self::CHANNEL_ID, ['channel.only.test']);
|
||||
|
||||
Http::assertSent(fn ($request) => $request->data() === [
|
||||
'deployment_domains' => ['channel.only.test'],
|
||||
'reporting_domains' => ['report.test'],
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_reports_builder_errors(): void
|
||||
{
|
||||
Http::fake(['builder.test/*' => Http::response(['message' => 'bad template'], 500)]);
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('生成渠道资源失败 (500): bad template');
|
||||
|
||||
app(ChannelProjectService::class)->generate(self::CHANNEL_ID);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_reports_connection_timeouts(): void
|
||||
{
|
||||
Http::fake(fn () => throw new ConnectionException('timed out'));
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('渠道构建服务连接失败: timed out');
|
||||
|
||||
app(ChannelProjectService::class)->generate(self::CHANNEL_ID);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function delete_is_authenticated_and_not_found_is_idempotent(): void
|
||||
{
|
||||
Http::fake(['builder.test/*' => Http::response([], 404)]);
|
||||
|
||||
app(ChannelProjectService::class)->deleteWebTree(self::CHANNEL_ID);
|
||||
|
||||
Http::assertSent(fn ($request) => $request->method() === 'DELETE'
|
||||
&& $request->url() === 'https://builder.test/v1/channels/'.self::CHANNEL_ID
|
||||
&& $request->hasHeader('Authorization', 'Bearer secret-token'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function delete_reports_builder_errors(): void
|
||||
{
|
||||
Http::fake(['builder.test/*' => Http::response('unavailable', 503)]);
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('删除渠道资源失败 (503): unavailable');
|
||||
|
||||
app(ChannelProjectService::class)->deleteWebTree(self::CHANNEL_ID);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function support_links_prefer_channel_domains_then_env_fallback(): void
|
||||
{
|
||||
config([
|
||||
'coruna.channel_domains' => ['fallback.test'],
|
||||
'coruna.deployment_domains' => ['fallback.test'],
|
||||
'coruna.static_site.base_url' => 'https://static.test/assets',
|
||||
]);
|
||||
|
||||
$withStored = new Channel([
|
||||
'channel_id' => self::CHANNEL_ID,
|
||||
'domains' => ['channel.test'],
|
||||
]);
|
||||
$this->assertSame([
|
||||
'https://static.test/assets/channel/'.self::CHANNEL_ID.'/web/support.html',
|
||||
'https://channel.test/channel/'.self::CHANNEL_ID.'/web/support.html',
|
||||
], $withStored->supportLinks());
|
||||
|
||||
$fallback = new Channel(['channel_id' => self::CHANNEL_ID, 'domains' => []]);
|
||||
$this->assertSame([
|
||||
'https://static.test/assets/channel/'.self::CHANNEL_ID.'/web/support.html',
|
||||
'https://fallback.test/channel/'.self::CHANNEL_ID.'/web/support.html',
|
||||
], $fallback->supportLinks());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function failed_database_insert_compensates_with_remote_delete(): void
|
||||
{
|
||||
Http::fake([
|
||||
'builder.test/*' => Http::sequence()
|
||||
->push(['ok' => true], 200)
|
||||
->push([], 204),
|
||||
]);
|
||||
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
||||
|
||||
$originalDispatcher = Channel::getEventDispatcher();
|
||||
Channel::setEventDispatcher(new Dispatcher($this->app));
|
||||
Channel::creating(static fn () => throw new RuntimeException('database rejected insert'));
|
||||
|
||||
try {
|
||||
$this->actingAs($admin, 'admin')
|
||||
->post(route('admin.channels.store'), [
|
||||
'channel_id' => self::CHANNEL_ID,
|
||||
'user_id' => 0,
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonPath('code', 1);
|
||||
} finally {
|
||||
Channel::setEventDispatcher($originalDispatcher);
|
||||
}
|
||||
|
||||
Http::assertSentCount(2);
|
||||
Http::assertSent(fn ($request) => $request->method() === 'DELETE');
|
||||
$this->assertDatabaseMissing('channels', ['channel_id' => self::CHANNEL_ID]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function duplicate_insert_loser_does_not_delete_winners_remote_project(): void
|
||||
{
|
||||
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
||||
$projects = Mockery::mock(ChannelProjectService::class);
|
||||
$projects->shouldReceive('generate')->once()->andReturnUsing(function (): void {
|
||||
Channel::query()->create([
|
||||
'channel_id' => self::CHANNEL_ID,
|
||||
'user_id' => 0,
|
||||
'status' => 1,
|
||||
]);
|
||||
});
|
||||
$projects->shouldNotReceive('deleteWebTree');
|
||||
$this->app->instance(ChannelProjectService::class, $projects);
|
||||
|
||||
$this->actingAs($admin, 'admin')
|
||||
->post(route('admin.channels.store'), [
|
||||
'channel_id' => self::CHANNEL_ID,
|
||||
'user_id' => 0,
|
||||
])
|
||||
->assertStatus(422)
|
||||
->assertJsonPath('code', 1);
|
||||
|
||||
$this->assertDatabaseCount('channels', 1);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function destroy_deletes_remote_project_before_database_row(): void
|
||||
{
|
||||
Http::fake(['builder.test/*' => Http::response([], 204)]);
|
||||
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
||||
$channel = Channel::query()->create([
|
||||
'channel_id' => self::CHANNEL_ID,
|
||||
'user_id' => 0,
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
$this->actingAs($admin, 'admin')
|
||||
->delete(route('admin.channels.destroy', $channel))
|
||||
->assertOk()
|
||||
->assertJsonPath('code', 0);
|
||||
|
||||
Http::assertSent(fn ($request) => $request->method() === 'DELETE');
|
||||
$this->assertDatabaseMissing('channels', ['id' => $channel->id]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function destroy_keeps_database_row_when_remote_delete_fails(): void
|
||||
{
|
||||
Http::fake(['builder.test/*' => Http::response(['message' => 'busy'], 503)]);
|
||||
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
||||
$channel = Channel::query()->create([
|
||||
'channel_id' => self::CHANNEL_ID,
|
||||
'user_id' => 0,
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
$this->actingAs($admin, 'admin')
|
||||
->delete(route('admin.channels.destroy', $channel))
|
||||
->assertStatus(422)
|
||||
->assertJsonPath('code', 1);
|
||||
|
||||
$this->assertDatabaseHas('channels', ['id' => $channel->id]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user