755 lines
30 KiB
PHP
755 lines
30 KiB
PHP
<?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\Support\Facades\Process;
|
|
use Mockery;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use RuntimeException;
|
|
use Tests\TestCase;
|
|
|
|
class ChannelProjectServiceTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private const CHANNEL_ID = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
|
|
|
|
private const NEW_CHANNEL_ID = '3.1.07';
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
config([
|
|
'coruna.channel_builder.python' => 'python3',
|
|
'coruna.channel_builder.artifact_root' => storage_path('app/channel-artifacts-test'),
|
|
'coruna.channel_builder.timeout' => 30,
|
|
'coruna.channel_builder.seed' => '11111111111111111111111111111111',
|
|
'coruna.channel_builder_new.python' => 'python3',
|
|
'coruna.channel_builder_new.state_root' => storage_path('app/channel-builder-new-test'),
|
|
'coruna.channel_domains' => ['fallback.test'],
|
|
'coruna.xxbb.channel_c' => '33333333333333333333333333333333',
|
|
]);
|
|
}
|
|
|
|
private function fakeBuildResult(array $overrides = []): string
|
|
{
|
|
$payload = array_merge([
|
|
'schema_version' => 1,
|
|
'status' => 'built',
|
|
'channel_id' => self::CHANNEL_ID,
|
|
'seeds' => [
|
|
'deployment_seed' => '11111111111111111111111111111111',
|
|
'reporting_seed' => '11111111111111111111111111111111',
|
|
],
|
|
'domains' => [
|
|
'deployment' => ['www.dep.example'],
|
|
'reporting' => ['www.dep.example'],
|
|
],
|
|
'seeds_initialized' => true,
|
|
'sync_rebuilt' => true,
|
|
'support_path' => '/web/'.self::CHANNEL_ID.'/support.html',
|
|
'daily_path' => '/sync/daily.html',
|
|
], $overrides);
|
|
|
|
return "log line\n".ChannelProjectService::RESULT_MARKER.json_encode($payload)."\n";
|
|
}
|
|
|
|
private function stageFakeWeifile(): string
|
|
{
|
|
$dir = storage_path('app/channel-builder-new-test/out/weifile');
|
|
if (! is_dir($dir) && ! mkdir($dir, 0775, true) && ! is_dir($dir)) {
|
|
$this->fail('unable to create staged weifile dir');
|
|
}
|
|
file_put_contents($dir.'/index.js', 'const V="CACACACA";');
|
|
file_put_contents($dir.'/weifile.html', '<script src="index.js"></script>');
|
|
|
|
$details = storage_path('app/channel-artifacts-test/details');
|
|
if (! is_dir($details) && ! mkdir($details, 0775, true) && ! is_dir($details)) {
|
|
$this->fail('unable to create details dir');
|
|
}
|
|
file_put_contents($details.'/show.html', 'show');
|
|
|
|
return $dir;
|
|
}
|
|
|
|
#[Test]
|
|
public function it_invokes_new_project_script_and_returns_seeds(): void
|
|
{
|
|
Process::fake([
|
|
'*' => Process::result(output: $this->fakeBuildResult()),
|
|
]);
|
|
|
|
$result = app(ChannelProjectService::class)->generate(self::CHANNEL_ID, 'blank');
|
|
|
|
$this->assertTrue($result['seeds_initialized']);
|
|
$this->assertSame('11111111111111111111111111111111', $result['seeds']['deployment_seed']);
|
|
$this->assertSame(['www.dep.example'], $result['domains']['deployment']);
|
|
$this->assertSame('/web/'.self::CHANNEL_ID.'/support.html', $result['support_path']);
|
|
|
|
Process::assertRan(function ($process) {
|
|
$cmd = $process->command;
|
|
if (is_array($cmd)) {
|
|
$joined = implode(' ', $cmd);
|
|
} else {
|
|
$joined = (string) $cmd;
|
|
}
|
|
|
|
return str_contains($joined, 'new_project.py')
|
|
&& str_contains($joined, self::CHANNEL_ID)
|
|
&& str_contains($joined, '--support-template')
|
|
&& str_contains($joined, 'blank')
|
|
&& str_contains($joined, '--deployment-seed')
|
|
&& str_contains($joined, '11111111111111111111111111111111')
|
|
&& str_contains($joined, '--reporting-seed');
|
|
});
|
|
}
|
|
|
|
#[Test]
|
|
public function it_forwards_env_seed_and_ignores_request_seeds(): void
|
|
{
|
|
Process::fake([
|
|
'*' => Process::result(output: $this->fakeBuildResult(['seeds_initialized' => false, 'sync_rebuilt' => false])),
|
|
]);
|
|
|
|
app(ChannelProjectService::class)->generate(
|
|
self::CHANNEL_ID,
|
|
'test',
|
|
'cccccccccccccccccccccccccccccccc',
|
|
'dddddddddddddddddddddddddddddddd',
|
|
);
|
|
|
|
Process::assertRan(function ($process) {
|
|
$joined = is_array($process->command) ? implode(' ', $process->command) : (string) $process->command;
|
|
|
|
return str_contains($joined, '--deployment-seed')
|
|
&& str_contains($joined, '11111111111111111111111111111111')
|
|
&& str_contains($joined, '--reporting-seed')
|
|
&& ! str_contains($joined, 'cccccccccccccccccccccccccccccccc')
|
|
&& ! str_contains($joined, 'dddddddddddddddddddddddddddddddd');
|
|
});
|
|
}
|
|
|
|
#[Test]
|
|
public function old_builder_requires_env_seed(): void
|
|
{
|
|
config(['coruna.channel_builder.seed' => '']);
|
|
|
|
$this->expectException(RuntimeException::class);
|
|
$this->expectExceptionMessage('CORUNA_CHANNEL_SEED');
|
|
|
|
app(ChannelProjectService::class)->generate(self::CHANNEL_ID, 'test');
|
|
}
|
|
|
|
#[Test]
|
|
public function it_reports_builder_errors(): void
|
|
{
|
|
Process::fake([
|
|
'*' => Process::result(output: '', errorOutput: 'boom', exitCode: 2),
|
|
]);
|
|
|
|
$this->expectException(RuntimeException::class);
|
|
$this->expectExceptionMessage('生成渠道资源失败: boom');
|
|
|
|
app(ChannelProjectService::class)->generate(self::CHANNEL_ID);
|
|
}
|
|
|
|
#[Test]
|
|
public function delete_invokes_delete_script(): void
|
|
{
|
|
Process::fake([
|
|
'*' => Process::result(output: ChannelProjectService::RESULT_MARKER.json_encode([
|
|
'status' => 'deleted',
|
|
'channel_id' => self::CHANNEL_ID,
|
|
'removed' => true,
|
|
])."\n"),
|
|
]);
|
|
|
|
app(ChannelProjectService::class)->deleteWebTree(self::CHANNEL_ID);
|
|
|
|
Process::assertRan(function ($process) {
|
|
$joined = is_array($process->command) ? implode(' ', $process->command) : (string) $process->command;
|
|
|
|
return str_contains($joined, 'delete_channel_web.py')
|
|
&& str_contains($joined, self::CHANNEL_ID);
|
|
});
|
|
}
|
|
|
|
#[Test]
|
|
public function support_links_use_online_web_path(): void
|
|
{
|
|
$withStored = new Channel([
|
|
'channel_id' => self::CHANNEL_ID,
|
|
'domains' => ['channel.test'],
|
|
]);
|
|
$this->assertSame([
|
|
'https://channel.test/web/'.self::CHANNEL_ID.'/support.html',
|
|
], $withStored->supportLinks());
|
|
|
|
$fallback = new Channel(['channel_id' => self::CHANNEL_ID, 'domains' => []]);
|
|
$this->assertSame([
|
|
'https://fallback.test/web/'.self::CHANNEL_ID.'/support.html',
|
|
], $fallback->supportLinks());
|
|
}
|
|
|
|
#[Test]
|
|
public function failed_database_insert_compensates_with_delete(): void
|
|
{
|
|
$this->stageFakeWeifile();
|
|
Process::fake([
|
|
'*' => Process::result(output: $this->fakeBuildResult([
|
|
'builder_type' => 'new',
|
|
'channel_id' => self::NEW_CHANNEL_ID,
|
|
'landing_path' => '/channel/'.self::NEW_CHANNEL_ID.'/weifile/weifile.html',
|
|
'details_path' => '/channel/'.self::NEW_CHANNEL_ID.'/details/',
|
|
'channel_dir' => '/channel/'.self::NEW_CHANNEL_ID,
|
|
])),
|
|
]);
|
|
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
|
$channelDir = storage_path('app/channel-artifacts-test/channel/'.self::NEW_CHANNEL_ID);
|
|
if (! is_dir($channelDir) && ! mkdir($channelDir, 0775, true) && ! is_dir($channelDir)) {
|
|
$this->fail('unable to create channel dir for compensation test');
|
|
}
|
|
|
|
$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::NEW_CHANNEL_ID,
|
|
'user_id' => 0,
|
|
])
|
|
->assertStatus(422)
|
|
->assertJsonPath('code', 1);
|
|
} finally {
|
|
Channel::setEventDispatcher($originalDispatcher);
|
|
}
|
|
|
|
$this->assertDirectoryDoesNotExist($channelDir);
|
|
$this->assertDatabaseMissing('channels', ['channel_id' => self::NEW_CHANNEL_ID]);
|
|
}
|
|
|
|
#[Test]
|
|
public function duplicate_insert_loser_does_not_delete_winners_project(): void
|
|
{
|
|
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
|
$projects = Mockery::mock(ChannelProjectService::class);
|
|
$projects->shouldReceive('generate')->once()->andReturnUsing(function (): array {
|
|
Channel::query()->create([
|
|
'channel_id' => self::NEW_CHANNEL_ID,
|
|
'builder_type' => Channel::BUILDER_NEW,
|
|
'user_id' => 0,
|
|
'status' => 1,
|
|
]);
|
|
|
|
return [
|
|
'channel_id' => self::NEW_CHANNEL_ID,
|
|
'seeds' => ['deployment_seed' => 'a', 'reporting_seed' => 'b'],
|
|
'domains' => ['deployment' => [], 'reporting' => []],
|
|
'seeds_initialized' => false,
|
|
'sync_rebuilt' => false,
|
|
'support_path' => '/channel/'.self::NEW_CHANNEL_ID.'/weifile/weifile.html',
|
|
'daily_path' => '/channel/'.self::NEW_CHANNEL_ID.'/details/',
|
|
];
|
|
});
|
|
$projects->shouldNotReceive('deleteWebTree');
|
|
$this->app->instance(ChannelProjectService::class, $projects);
|
|
|
|
$this->actingAs($admin, 'admin')
|
|
->post(route('admin.channels.store'), [
|
|
'channel_id' => self::NEW_CHANNEL_ID,
|
|
'user_id' => 0,
|
|
])
|
|
->assertStatus(422)
|
|
->assertJsonPath('code', 1);
|
|
|
|
$this->assertDatabaseCount('channels', 1);
|
|
}
|
|
|
|
#[Test]
|
|
public function destroy_deletes_web_tree_before_database_row(): void
|
|
{
|
|
Process::fake([
|
|
'*' => Process::result(output: ChannelProjectService::RESULT_MARKER.json_encode([
|
|
'status' => 'deleted',
|
|
'removed' => true,
|
|
])."\n"),
|
|
]);
|
|
$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);
|
|
|
|
Process::assertRan(fn ($process) => str_contains(
|
|
is_array($process->command) ? implode(' ', $process->command) : (string) $process->command,
|
|
'delete_channel_web.py'
|
|
));
|
|
$this->assertDatabaseMissing('channels', ['id' => $channel->id]);
|
|
}
|
|
|
|
#[Test]
|
|
public function destroy_keeps_database_row_when_delete_fails(): void
|
|
{
|
|
Process::fake([
|
|
'*' => Process::result(output: '', errorOutput: 'busy', exitCode: 1),
|
|
]);
|
|
$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]);
|
|
}
|
|
|
|
#[Test]
|
|
public function store_returns_seeds_and_domains(): void
|
|
{
|
|
$this->stageFakeWeifile();
|
|
Process::fake([
|
|
'*' => Process::result(output: $this->fakeBuildResult([
|
|
'builder_type' => 'new',
|
|
'channel_id' => self::NEW_CHANNEL_ID,
|
|
'landing_path' => '/channel/'.self::NEW_CHANNEL_ID.'/weifile/weifile.html',
|
|
'details_path' => '/channel/'.self::NEW_CHANNEL_ID.'/details/',
|
|
'daily_path' => '/channel/'.self::NEW_CHANNEL_ID.'/details/',
|
|
'channel_dir' => '/channel/'.self::NEW_CHANNEL_ID,
|
|
])),
|
|
]);
|
|
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
|
|
|
$this->actingAs($admin, 'admin')
|
|
->post(route('admin.channels.store'), [
|
|
'channel_id' => self::NEW_CHANNEL_ID,
|
|
'user_id' => 0,
|
|
])
|
|
->assertOk()
|
|
->assertJsonPath('code', 0)
|
|
->assertJsonPath('data.builder_type', Channel::BUILDER_NEW)
|
|
->assertJsonPath('data.seeds.deployment_seed', '11111111111111111111111111111111')
|
|
->assertJsonPath('data.domains.deployment.0', 'www.dep.example')
|
|
->assertJsonPath('data.daily_path', '/channel/'.self::NEW_CHANNEL_ID.'/details/');
|
|
|
|
$this->assertDatabaseHas('channels', [
|
|
'channel_id' => self::NEW_CHANNEL_ID,
|
|
'builder_type' => Channel::BUILDER_NEW,
|
|
]);
|
|
$channel = Channel::query()->where('channel_id', self::NEW_CHANNEL_ID)->first();
|
|
$this->assertSame([], $channel->domains ?? []);
|
|
$this->assertSame(Channel::BUILDER_NEW, $channel->builderType());
|
|
}
|
|
|
|
#[Test]
|
|
public function store_accepts_old_builder_channel_id(): void
|
|
{
|
|
Process::fake([
|
|
'*' => Process::result(output: $this->fakeBuildResult()),
|
|
]);
|
|
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
|
|
|
$this->actingAs($admin, 'admin')
|
|
->post(route('admin.channels.store'), [
|
|
'channel_id' => self::CHANNEL_ID,
|
|
'builder_type' => Channel::BUILDER_OLD,
|
|
'user_id' => 0,
|
|
])
|
|
->assertOk()
|
|
->assertJsonPath('code', 0)
|
|
->assertJsonPath('data.builder_type', Channel::BUILDER_OLD)
|
|
->assertJsonPath('data.seeds.deployment_seed', '11111111111111111111111111111111')
|
|
->assertJsonPath('data.domains.deployment.0', 'www.dep.example')
|
|
->assertJsonPath('data.support_path', '/web/'.self::CHANNEL_ID.'/support.html');
|
|
|
|
$this->assertDatabaseHas('channels', [
|
|
'channel_id' => self::CHANNEL_ID,
|
|
'builder_type' => Channel::BUILDER_OLD,
|
|
]);
|
|
|
|
Process::assertRan(function ($process) {
|
|
$joined = is_array($process->command) ? implode(' ', $process->command) : (string) $process->command;
|
|
|
|
return str_contains($joined, 'new_project.py')
|
|
&& str_contains($joined, self::CHANNEL_ID);
|
|
});
|
|
}
|
|
|
|
#[Test]
|
|
public function store_rejects_new_format_id_for_old_builder(): void
|
|
{
|
|
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
|
|
|
$this->actingAs($admin, 'admin')
|
|
->post(route('admin.channels.store'), [
|
|
'channel_id' => self::NEW_CHANNEL_ID,
|
|
'builder_type' => Channel::BUILDER_OLD,
|
|
'user_id' => 0,
|
|
])
|
|
->assertSessionHasErrors('channel_id');
|
|
|
|
$this->assertDatabaseMissing('channels', ['channel_id' => self::NEW_CHANNEL_ID]);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_packs_new_channel_from_staged_weifile(): void
|
|
{
|
|
$this->stageFakeWeifile();
|
|
Process::fake([
|
|
'*' => Process::result(output: $this->fakeBuildResult([
|
|
'builder_type' => 'new',
|
|
'channel_id' => self::NEW_CHANNEL_ID,
|
|
'channel_ver' => self::NEW_CHANNEL_ID,
|
|
'weifile_path' => null,
|
|
'support_path' => '',
|
|
'landing_path' => '/channel/'.self::NEW_CHANNEL_ID.'/weifile/weifile.html',
|
|
'details_path' => '/channel/'.self::NEW_CHANNEL_ID.'/details/',
|
|
'daily_path' => '/channel/'.self::NEW_CHANNEL_ID.'/details/',
|
|
'channel_dir' => '/channel/'.self::NEW_CHANNEL_ID,
|
|
'domains' => ['deployment' => [], 'reporting' => []],
|
|
])),
|
|
]);
|
|
|
|
$result = app(ChannelProjectService::class)->generate(
|
|
self::NEW_CHANNEL_ID,
|
|
'test',
|
|
null,
|
|
null,
|
|
Channel::BUILDER_NEW,
|
|
);
|
|
|
|
$this->assertSame(Channel::BUILDER_NEW, $result['builder_type']);
|
|
$this->assertNull($result['weifile_path']);
|
|
$this->assertSame('/channel/'.self::NEW_CHANNEL_ID.'/weifile/weifile.html', $result['support_path']);
|
|
$this->assertSame('/channel/'.self::NEW_CHANNEL_ID.'/details/', $result['daily_path']);
|
|
$this->assertSame('/channel/'.self::NEW_CHANNEL_ID, $result['channel_dir']);
|
|
|
|
Process::assertRan(function ($process) {
|
|
$joined = is_array($process->command) ? implode(' ', $process->command) : (string) $process->command;
|
|
|
|
return str_contains($joined, 'build.py')
|
|
&& str_contains($joined, '--channel-c')
|
|
&& str_contains($joined, '33333333333333333333333333333333')
|
|
&& str_contains($joined, '--apply')
|
|
&& ! str_contains($joined, '--random-c');
|
|
});
|
|
Process::assertRan(function ($process) {
|
|
$joined = is_array($process->command) ? implode(' ', $process->command) : (string) $process->command;
|
|
|
|
return str_contains($joined, 'pack_channel.py')
|
|
&& str_contains($joined, '--channel-ver')
|
|
&& str_contains($joined, self::NEW_CHANNEL_ID)
|
|
&& str_contains($joined, '--channel-out')
|
|
&& str_contains($joined, '--landing-template')
|
|
&& str_contains($joined, 'test');
|
|
});
|
|
}
|
|
|
|
#[Test]
|
|
public function new_builder_requires_env_channel_c(): void
|
|
{
|
|
config(['coruna.xxbb.channel_c' => '']);
|
|
|
|
$this->expectException(RuntimeException::class);
|
|
$this->expectExceptionMessage('XXBB_CHANNEL_C');
|
|
|
|
app(ChannelProjectService::class)->generate(
|
|
self::NEW_CHANNEL_ID,
|
|
'test',
|
|
null,
|
|
null,
|
|
Channel::BUILDER_NEW,
|
|
);
|
|
}
|
|
|
|
#[Test]
|
|
public function new_builder_ignores_seed_mismatch(): void
|
|
{
|
|
$this->stageFakeWeifile();
|
|
Process::fake([
|
|
'*' => Process::result(output: $this->fakeBuildResult([
|
|
'builder_type' => 'new',
|
|
'landing_path' => '/channel/'.self::NEW_CHANNEL_ID.'/weifile/weifile.html',
|
|
'details_path' => '/channel/'.self::NEW_CHANNEL_ID.'/details/',
|
|
'channel_dir' => '/channel/'.self::NEW_CHANNEL_ID,
|
|
])),
|
|
]);
|
|
|
|
$result = app(ChannelProjectService::class)->generate(
|
|
self::NEW_CHANNEL_ID,
|
|
'test',
|
|
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
|
Channel::BUILDER_NEW,
|
|
);
|
|
|
|
$this->assertSame(self::NEW_CHANNEL_ID, $result['channel_id']);
|
|
}
|
|
|
|
#[Test]
|
|
public function support_links_use_channel_path_for_new_builder(): void
|
|
{
|
|
$channel = new Channel([
|
|
'channel_id' => self::NEW_CHANNEL_ID,
|
|
'builder_type' => Channel::BUILDER_NEW,
|
|
'domains' => ['channel.test'],
|
|
]);
|
|
$this->assertSame('/channel/'.self::NEW_CHANNEL_ID.'/weifile/weifile.html', $channel->landingPath());
|
|
$this->assertSame(['https://channel.test/channel/'.self::NEW_CHANNEL_ID.'/weifile/weifile.html'], $channel->supportLinks());
|
|
}
|
|
|
|
#[Test]
|
|
public function store_new_builder_returns_channel_dir(): void
|
|
{
|
|
$this->stageFakeWeifile();
|
|
Process::fake([
|
|
'*' => Process::result(output: $this->fakeBuildResult([
|
|
'weifile_path' => null,
|
|
'support_path' => '',
|
|
'landing_path' => '/channel/'.self::NEW_CHANNEL_ID.'/weifile/weifile.html',
|
|
'details_path' => '/channel/'.self::NEW_CHANNEL_ID.'/details/',
|
|
'daily_path' => '/channel/'.self::NEW_CHANNEL_ID.'/details/',
|
|
'channel_dir' => '/channel/'.self::NEW_CHANNEL_ID,
|
|
])),
|
|
]);
|
|
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
|
|
|
$response = $this->actingAs($admin, 'admin')
|
|
->post(route('admin.channels.store'), [
|
|
'channel_id' => self::NEW_CHANNEL_ID,
|
|
'user_id' => 0,
|
|
])
|
|
->assertOk()
|
|
->assertJsonPath('code', 0)
|
|
->assertJsonPath('data.builder_type', Channel::BUILDER_NEW)
|
|
->assertJsonPath('data.channel_dir', '/channel/'.self::NEW_CHANNEL_ID)
|
|
->assertJsonPath('data.support_path', '/channel/'.self::NEW_CHANNEL_ID.'/weifile/weifile.html');
|
|
|
|
$channel = Channel::query()->where('channel_id', self::NEW_CHANNEL_ID)->first();
|
|
$this->assertNotNull($channel);
|
|
$this->assertSame(Channel::BUILDER_NEW, $channel->builderType());
|
|
$this->assertNull($response->json('data.weifile_path'));
|
|
$this->assertNull($response->json('data.zip_path'));
|
|
$this->assertNull($response->json('data.download_url'));
|
|
|
|
Process::assertRan(function ($process) {
|
|
$joined = is_array($process->command) ? implode(' ', $process->command) : (string) $process->command;
|
|
|
|
return str_contains($joined, 'pack_channel.py')
|
|
&& str_contains($joined, self::NEW_CHANNEL_ID);
|
|
});
|
|
}
|
|
|
|
#[Test]
|
|
public function destroy_new_builder_deletes_channel_dir_not_shared_details(): void
|
|
{
|
|
Process::fake();
|
|
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
|
$channel = Channel::query()->create([
|
|
'channel_id' => self::NEW_CHANNEL_ID,
|
|
'builder_type' => Channel::BUILDER_NEW,
|
|
'user_id' => 0,
|
|
'status' => 1,
|
|
]);
|
|
$channelDir = storage_path('app/channel-artifacts-test/channel/'.self::NEW_CHANNEL_ID);
|
|
if (! is_dir($channelDir.'/weifile') && ! mkdir($channelDir.'/weifile', 0775, true) && ! is_dir($channelDir.'/weifile')) {
|
|
$this->fail('unable to create channel weifile dir');
|
|
}
|
|
if (! is_dir($channelDir.'/details') && ! mkdir($channelDir.'/details', 0775, true) && ! is_dir($channelDir.'/details')) {
|
|
$this->fail('unable to create channel dir');
|
|
}
|
|
file_put_contents($channelDir.'/weifile/weifile.html', 'landing');
|
|
$details = storage_path('app/channel-artifacts-test/details');
|
|
if (! is_dir($details)) {
|
|
mkdir($details, 0775, true);
|
|
}
|
|
file_put_contents($details.'/show.html', 'keep');
|
|
|
|
$this->actingAs($admin, 'admin')
|
|
->delete(route('admin.channels.destroy', $channel))
|
|
->assertOk()
|
|
->assertJsonPath('code', 0);
|
|
|
|
Process::assertDidntRun(function ($process) {
|
|
$joined = is_array($process->command) ? implode(' ', $process->command) : (string) $process->command;
|
|
|
|
return str_contains($joined, 'delete_channel.py')
|
|
|| str_contains($joined, 'delete_channel_web.py');
|
|
});
|
|
$this->assertDirectoryDoesNotExist($channelDir);
|
|
$this->assertFileExists($details.'/show.html');
|
|
$this->assertDatabaseMissing('channels', ['id' => $channel->id]);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_rebuilds_existing_new_channels_with_shared_build_once(): void
|
|
{
|
|
$this->stageFakeWeifile();
|
|
Channel::query()->create([
|
|
'channel_id' => '0.0.01',
|
|
'builder_type' => Channel::BUILDER_NEW,
|
|
'user_id' => 0,
|
|
'status' => 1,
|
|
]);
|
|
Channel::query()->create([
|
|
'channel_id' => self::NEW_CHANNEL_ID,
|
|
'builder_type' => Channel::BUILDER_NEW,
|
|
'user_id' => 0,
|
|
'status' => 1,
|
|
]);
|
|
Channel::query()->create([
|
|
'channel_id' => self::CHANNEL_ID,
|
|
'builder_type' => Channel::BUILDER_OLD,
|
|
'user_id' => 0,
|
|
'status' => 1,
|
|
]);
|
|
Process::fake([
|
|
'*' => Process::result(output: $this->fakeBuildResult([
|
|
'builder_type' => 'new',
|
|
'landing_path' => '/channel/0.0.01/weifile/weifile.html',
|
|
'details_path' => '/channel/0.0.01/details/',
|
|
'channel_dir' => '/channel/0.0.01',
|
|
])),
|
|
]);
|
|
|
|
$result = app(ChannelProjectService::class)->rebuildNewChannels();
|
|
|
|
$this->assertSame(['0.0.01', self::NEW_CHANNEL_ID], array_column($result['channels'], 'channel_id'));
|
|
$this->assertArrayHasKey('shared', $result);
|
|
|
|
Process::assertRanTimes(function ($process) {
|
|
$joined = is_array($process->command) ? implode(' ', $process->command) : (string) $process->command;
|
|
|
|
return str_contains($joined, 'build.py') && str_contains($joined, '--channel-c');
|
|
}, 1);
|
|
Process::assertRanTimes(function ($process) {
|
|
$joined = is_array($process->command) ? implode(' ', $process->command) : (string) $process->command;
|
|
|
|
return str_contains($joined, 'pack_channel.py');
|
|
}, 2);
|
|
Process::assertRan(function ($process) {
|
|
$joined = is_array($process->command) ? implode(' ', $process->command) : (string) $process->command;
|
|
|
|
return str_contains($joined, 'pack_channel.py') && str_contains($joined, '0.0.01');
|
|
});
|
|
Process::assertRan(function ($process) {
|
|
$joined = is_array($process->command) ? implode(' ', $process->command) : (string) $process->command;
|
|
|
|
return str_contains($joined, 'pack_channel.py') && str_contains($joined, self::NEW_CHANNEL_ID);
|
|
});
|
|
}
|
|
|
|
#[Test]
|
|
public function it_rebuilds_existing_old_channels_in_place(): void
|
|
{
|
|
$otherOld = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';
|
|
Channel::query()->create([
|
|
'channel_id' => self::CHANNEL_ID,
|
|
'builder_type' => Channel::BUILDER_OLD,
|
|
'user_id' => 0,
|
|
'status' => 1,
|
|
]);
|
|
Channel::query()->create([
|
|
'channel_id' => $otherOld,
|
|
'builder_type' => Channel::BUILDER_OLD,
|
|
'user_id' => 0,
|
|
'status' => 1,
|
|
]);
|
|
Channel::query()->create([
|
|
'channel_id' => self::NEW_CHANNEL_ID,
|
|
'builder_type' => Channel::BUILDER_NEW,
|
|
'user_id' => 0,
|
|
'status' => 1,
|
|
]);
|
|
Process::fake([
|
|
'*' => Process::result(output: $this->fakeBuildResult()),
|
|
]);
|
|
|
|
$result = app(ChannelProjectService::class)->rebuildOldChannels();
|
|
|
|
$this->assertCount(2, $result['channels']);
|
|
|
|
Process::assertRanTimes(function ($process) {
|
|
$joined = is_array($process->command) ? implode(' ', $process->command) : (string) $process->command;
|
|
|
|
return str_contains($joined, 'new_project.py')
|
|
&& str_contains($joined, '--deployment-seed')
|
|
&& str_contains($joined, '11111111111111111111111111111111');
|
|
}, 2);
|
|
Process::assertRan(function ($process) {
|
|
$joined = is_array($process->command) ? implode(' ', $process->command) : (string) $process->command;
|
|
|
|
return str_contains($joined, 'new_project.py') && str_contains($joined, self::CHANNEL_ID);
|
|
});
|
|
Process::assertRan(function ($process) {
|
|
$joined = is_array($process->command) ? implode(' ', $process->command) : (string) $process->command;
|
|
|
|
return str_contains($joined, 'new_project.py') && str_contains($joined, 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
|
|
});
|
|
}
|
|
|
|
#[Test]
|
|
public function it_rejects_invalid_old_channel_ids_when_repacking(): void
|
|
{
|
|
$this->expectException(RuntimeException::class);
|
|
$this->expectExceptionMessage('旧版渠道 ID 必须是 32 位 hex');
|
|
|
|
app(ChannelProjectService::class)->resolveOldChannelIds(['not-a-channel']);
|
|
}
|
|
|
|
#[Test]
|
|
public function coruna_repack_dry_run_lists_old_channels(): void
|
|
{
|
|
Channel::query()->create([
|
|
'channel_id' => self::CHANNEL_ID,
|
|
'builder_type' => Channel::BUILDER_OLD,
|
|
'user_id' => 0,
|
|
'status' => 1,
|
|
]);
|
|
Channel::query()->create([
|
|
'channel_id' => self::NEW_CHANNEL_ID,
|
|
'builder_type' => Channel::BUILDER_NEW,
|
|
'user_id' => 0,
|
|
'status' => 1,
|
|
]);
|
|
Process::fake();
|
|
|
|
$this->artisan('coruna:repack', ['--dry-run' => true])
|
|
->expectsOutputToContain('将重打 1 个旧版渠道')
|
|
->expectsOutputToContain('/web/'.self::CHANNEL_ID.'/support.html')
|
|
->assertSuccessful();
|
|
|
|
Process::assertNothingRan();
|
|
}
|
|
|
|
#[Test]
|
|
public function version_format_required_for_new_channel_ids(): void
|
|
{
|
|
$this->assertNull(Channel::normalizeNewChannelId('FAFA9988'));
|
|
$this->assertNull(Channel::normalizeNewChannelId('2.2.6'));
|
|
$this->assertNull(Channel::normalizeNewChannelId('22.2.66'));
|
|
$this->assertSame('3.1.07', Channel::normalizeNewChannelId('3.1.07'));
|
|
$this->assertSame('2.2.66', Channel::normalizeNewChannelId('2.2.66'));
|
|
$this->assertSame('A.B.C1', Channel::normalizeNewChannelId('a.b.c1'));
|
|
$this->assertSame('X.9.Z0', Channel::normalizeNewChannelId('x.9.z0'));
|
|
}
|
|
}
|