144 lines
7.2 KiB
PHP
144 lines
7.2 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Http\Controllers\Api\v1\ArticlesController;
|
|
use App\Models\Articles;
|
|
use App\Models\Categories;
|
|
use App\Services\ArticleRecommendations;
|
|
use App\Services\SiteMode;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Http\Exceptions\HttpResponseException;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Tests\TestCase;
|
|
|
|
#[\PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses]
|
|
#[\PHPUnit\Framework\Attributes\PreserveGlobalState(false)]
|
|
final class QuietModeTest extends TestCase
|
|
{
|
|
private string $state;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->state = sys_get_temp_dir().'/fna-quiet-'.bin2hex(random_bytes(8)).'.json';
|
|
config(['site_mode.state_path'=>$this->state, 'site_mode.quiet'=>false,
|
|
'cache.default'=>'array', 'responsecache.enabled'=>false,
|
|
'database.default'=>'sqlite', 'database.connections.sqlite.database'=>':memory:',
|
|
'scout.driver'=>null, 'editorial.indexnow_enabled'=>false]);
|
|
DB::purge('sqlite'); Http::preventStrayRequests();
|
|
Schema::create('categories', function (Blueprint $t) {
|
|
$t->id(); $t->string('sSlug'); $t->string('sName');
|
|
});
|
|
DB::table('categories')->insert([
|
|
['id'=>1,'sSlug'=>'freenode','sName'=>'免费节点'],
|
|
['id'=>2,'sSlug'=>'shadowrocketids','sName'=>'免费小火箭ID'],
|
|
['id'=>3,'sSlug'=>'client','sName'=>'新手教程'],
|
|
]);
|
|
Schema::create('articles', function (Blueprint $t) {
|
|
$t->id(); $t->integer('iCategoryId'); $t->string('sSlug');
|
|
$t->text('sContent'); $t->integer('iLikeCount')->default(0); $t->timestamps();
|
|
});
|
|
DB::table('articles')->insert(['id'=>7,'iCategoryId'=>1,'sSlug'=>'m7','sContent'=>'original']);
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
if (isset($this->state) && is_file($this->state)) unlink($this->state);
|
|
parent::tearDown();
|
|
}
|
|
|
|
private function paused(callable $action): void
|
|
{
|
|
try { $action(); $this->fail('Expected paused response.'); }
|
|
catch (HttpResponseException $e) {
|
|
$this->assertSame(503, $e->getResponse()->getStatusCode());
|
|
$this->assertSame('3600', $e->getResponse()->headers->get('Retry-After'));
|
|
$this->assertSame('FREE_NODE_UPDATES_PAUSED', json_decode($e->getResponse()->getContent(), true)['code']);
|
|
}
|
|
}
|
|
|
|
public function test_command_switch_is_persistent_reversible_and_private(): void
|
|
{
|
|
$this->assertFalse(SiteMode::quiet());
|
|
$this->artisan('site:quiet on')->assertExitCode(0);
|
|
$this->assertTrue(SiteMode::quiet());
|
|
$this->assertSame(0600, fileperms($this->state) & 0777);
|
|
$this->artisan('site:quiet status')->assertExitCode(0);
|
|
$this->assertTrue(json_decode(file_get_contents($this->state), true)['quiet']);
|
|
$this->artisan('site:quiet off')->assertExitCode(0);
|
|
$this->assertFalse(SiteMode::quiet());
|
|
$this->artisan('site:quiet invalid')->assertExitCode(1);
|
|
$this->assertFalse(SiteMode::quiet());
|
|
}
|
|
|
|
public function test_invalid_state_stays_paused_and_environment_fallback_works(): void
|
|
{
|
|
config(['site_mode.quiet'=>true]); $this->assertTrue(SiteMode::quiet());
|
|
config(['site_mode.quiet'=>false]);
|
|
file_put_contents($this->state, 'invalid'); $this->assertTrue(SiteMode::quiet());
|
|
file_put_contents($this->state, '{"quiet":"false"}'); $this->assertTrue(SiteMode::quiet());
|
|
}
|
|
|
|
public function test_receivers_pause_before_images_flows_and_any_database_write(): void
|
|
{
|
|
config(['site_mode.quiet'=>true]);
|
|
$controller = new ArticlesController();
|
|
$this->paused(fn () => $controller->receiveFreenode(new Request()));
|
|
$this->paused(fn () => $controller->receiveFreenode_laji(new Request()));
|
|
$this->paused(fn () => $controller->receive(new Request(['_raw'=>[
|
|
'id'=>8, 'opt'=>json_encode(['sCategoryName'=>'免费节点']),
|
|
]])));
|
|
// Moving an existing node article to another category must not bypass the pause.
|
|
$this->paused(fn () => $controller->receive(new Request(['_raw'=>[
|
|
'id'=>7, 'opt'=>json_encode(['sCategoryName'=>'新手教程']),
|
|
]])));
|
|
$this->assertSame(1, DB::table('articles')->count());
|
|
$this->assertSame('original', DB::table('articles')->where('id',7)->value('sContent'));
|
|
Http::assertNothingSent();
|
|
}
|
|
|
|
public function test_model_rejects_publication_changes_but_allows_metrics_and_other_types(): void
|
|
{
|
|
config(['site_mode.quiet'=>true]);
|
|
$existing = Articles::find(7); $existing->sContent = 'replacement';
|
|
$this->paused(fn () => $existing->save());
|
|
$this->assertSame('original', DB::table('articles')->where('id',7)->value('sContent'));
|
|
$new = new Articles(['iCategoryId'=>1,'sSlug'=>'fn-new','sContent'=>'new']);
|
|
$this->paused(fn () => $new->save());
|
|
$metrics = Articles::find(7); $metrics->iLikeCount = 10; $metrics->save();
|
|
$this->assertSame(10, DB::table('articles')->where('id',7)->value('iLikeCount'));
|
|
foreach ([2,3] as $id) SiteMode::guardArticle(new Articles(['iCategoryId'=>$id,'sContent'=>'unchanged scope']));
|
|
SiteMode::assertUpdatesAllowed('shadowrocketids');
|
|
$this->assertSame(1, DB::table('articles')->count());
|
|
config(['site_mode.quiet'=>false]); SiteMode::assertUpdatesAllowed('freenode');
|
|
}
|
|
|
|
public function test_promotions_disappear_without_mutating_original_body_or_copy_payload(): void
|
|
{
|
|
$category = new Categories(['sSlug'=>'freenode']);
|
|
$article = new Articles(['sContent'=>'<h2>机场推荐 - 福云:</h2><p>优惠</p><p><a href="https://example.invalid/">机场官网:福云</a></p><h2>订阅</h2><a data-clipboard-text="example-sub">复制</a>']);
|
|
$article->setRelation('oCategory', $category); $original = $article->sContent;
|
|
config(['site_mode.quiet'=>true]);
|
|
$result = ArticleRecommendations::prepare($article);
|
|
$this->assertFalse($result['enabled']);
|
|
$this->assertStringNotContainsString('福云', $result['body']);
|
|
$this->assertStringContainsString('data-clipboard-text="example-sub"', $result['body']);
|
|
$this->assertSame($original, $article->sContent);
|
|
$this->assertSame([], ArticleRecommendations::providers());
|
|
$this->assertStringNotContainsString('data-random-article-ad', view('components.article-ads')->render());
|
|
$this->assertStringNotContainsString('sponsored', view('components.article-ads')->render());
|
|
foreach (['dpl','hn','fuc'] as $provider) {
|
|
$this->assertStringNotContainsString('sponsored', view('components.'.$provider.'-article-ad')->render());
|
|
}
|
|
$this->assertStringNotContainsString('airport-recommendations', view('components.airport-recommendations')->render());
|
|
config(['site_mode.quiet'=>false]);
|
|
$this->assertTrue(ArticleRecommendations::prepare($article)['enabled']);
|
|
$this->assertStringContainsString('data-random-article-ad', view('components.article-ads')->render());
|
|
$this->assertSame($original, $article->sContent);
|
|
}
|
|
}
|