| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- <?php
- namespace app\api\lists {
- // Isolate list infrastructure; run the real list implementation and ThinkPHP ORM below.
- class BaseApiDataLists {
- protected string $lang = 'zh';
- protected array $searchWhere = [];
- public int $limitOffset = 0;
- public int $limitLength = 2;
- public function __construct(array $params = []) { $this->setParams($params); }
- public function setParams($params): self {
- $this->lang = $params['lang'] ?? 'zh';
- $this->searchWhere = isset($params['type']) ? [['type', '=', $params['type']]] : [];
- return $this;
- }
- }
- function time(): int { return $GLOBALS['banner_test_now']; }
- }
- namespace {
- error_reporting(E_ALL & ~E_DEPRECATED);
- function env($key, $default = '') {
- return ['app.host' => 'https://site.example.test', 'app.BOT_HOST' => 'https://bot.example.test'][$key] ?? $default;
- }
- $repo = getenv('BANNER_SITE_REPO') ?: dirname(__DIR__, 3) . '/melbet_sport-api';
- $stage = getenv('BANNER_SITE_STAGE') ?: $repo;
- $loader = require $repo . '/vendor/autoload.php';
- $loader->setPsr4('app\\', [$stage . '/app/', $repo . '/app/']);
- require $repo . '/app/common.php';
- $app = new \think\App($repo . '/');
- \think\Container::setInstance($app);
- $db = new \think\DbManager();
- $db->setConfig(['default' => 'isolated', 'connections' => ['isolated' => [
- 'type' => 'sqlite', 'database' => ':memory:', 'prefix' => '', 'fields_strict' => false, 'debug' => false,
- ]], 'auto_timestamp' => false]);
- $app->instance(\think\DbManager::class, $db);
- $app->instance('log', new \Psr\Log\NullLogger());
- $selectCount = 0;
- $db->event('before_select', static function () use (&$selectCount) { ++$selectCount; });
- $GLOBALS['banner_test_now'] = 1792800000;
- $now = $GLOBALS['banner_test_now'];
- $db->execute('CREATE TABLE bot_banner (id INTEGER PRIMARY KEY,title TEXT,image TEXT,link TEXT,type TEXT,sort INTEGER,lang TEXT,status INTEGER DEFAULT 1)');
- $db->execute('CREATE TABLE bot_activity_rewards (id INTEGER PRIMARY KEY,status INTEGER,start_time INTEGER,end_time INTEGER)');
- foreach ([
- [101, 1, $now - 100, $now + 100],
- [102, 1, $now - 100, $now],
- [103, 1, $now + 1, $now + 100],
- [104, 0, $now - 100, $now + 100],
- [105, 1, $now, $now + 100],
- ] as [$id, $status, $start, $end]) {
- \think\facade\Db::table('bot_activity_rewards')->insert(['id' => $id, 'status' => $status, 'start_time' => $start, 'end_time' => $end]);
- }
- function bannerRow(int $id, $link, array $overrides = []): void {
- \think\facade\Db::table('bot_banner')->insert($overrides + [
- 'id' => $id, 'title' => 'Banner ' . $id, 'image' => 'https://site.example.test/banner.png',
- 'link' => $link, 'type' => 'pc', 'sort' => 100 - $id, 'lang' => 'zh', 'status' => 1,
- ]);
- }
- bannerRow(1, '102'); // Expired first-ranked item must not create a pagination gap.
- bannerRow(2, '', ['status' => 0]);
- bannerRow(3, '101');
- bannerRow(4, '');
- bannerRow(5, '0');
- bannerRow(6, 'https://external.example.test/path');
- bannerRow(7, '999');
- bannerRow(8, '103');
- bannerRow(9, '104');
- bannerRow(10, '105');
- bannerRow(11, '', ['lang' => 'en']);
- bannerRow(12, '', ['type' => 'app']);
- bannerRow(13, null);
- bannerRow(14, '000101');
- bannerRow(15, '000');
- bannerRow(16, '101', ['status' => 0]);
- $checks = 0;
- function check($actual, $expected, string $label): void {
- global $checks;
- ++$checks;
- if ($actual !== $expected) throw new \RuntimeException($label . ': got ' . json_encode($actual) . ', expected ' . json_encode($expected));
- }
- $before = \think\facade\Db::table('bot_banner')->order('id')->select()->toArray();
- $list = new \app\api\lists\BannerLists(['type' => 'pc']);
- $queryCount = $selectCount;
- check($list->count(), 9, 'Unbound banners use manual status; bound banners use only activity lifecycle');
- check($selectCount - $queryCount, 2, 'One banner scan and one activity lookup');
- $queryCount = $selectCount;
- check(array_column($list->lists(), 'id'), [3, 4], 'Lifecycle filtering precedes pagination');
- check($selectCount, $queryCount, 'Count and page share cached visibility');
- $list->limitOffset = 2;
- check(array_column($list->lists(), 'id'), [5, 6], 'Zero and external URL are unbound');
- $list->limitOffset = 4;
- check(array_column($list->lists(), 'id'), [10, 13], 'Start boundary and null link are visible');
- $list->limitOffset = 6;
- check(array_column($list->lists(), 'id'), [14, 15], 'Leading zeros match the correct positive ID or unbound zero');
- $list->limitOffset = 8;
- check(array_column($list->lists(), 'id'), [16], 'Archived manual-off cannot hide a banner bound to an active activity');
- $list->limitOffset = 0;
- $item = $list->lists()[0];
- check(array_keys($item), ['id', 'type', 'image', 'link', 'title'], 'Public fields remain compatible');
- check($item['image'], 'https://bot.example.test/banner.png', 'Existing image host replacement is preserved');
- check($item['link'], '101', 'Public jump target preserved');
- check(\think\facade\Db::table('bot_banner')->order('id')->select()->toArray(), $before, 'Reads do not mutate manual status or other original fields');
- $english = new \app\api\lists\BannerLists(['type' => 'pc', 'lang' => 'en']);
- check(array_column($english->lists(), 'id'), [11], 'Language filter retained');
- $appList = new \app\api\lists\BannerLists(['type' => 'app']);
- check(array_column($appList->lists(), 'id'), [12], 'Platform filter retained');
- $english->setParams(['type' => 'app']);
- check(array_column($english->lists(), 'id'), [12], 'setParams clears cached rows and restores query filters');
- \think\facade\Db::table('bot_activity_rewards')->where('id', 101)->update(['status' => 0]);
- check((new \app\api\lists\BannerLists(['type' => 'pc']))->count(), 6, 'Activity off hides every linked banner regardless of archived manual status');
- \think\facade\Db::table('bot_activity_rewards')->where('id', 101)->update(['status' => 1]);
- check((new \app\api\lists\BannerLists(['type' => 'pc']))->count(), 9, 'Activity on restores every linked banner including archived manual-off');
- // Reopen an activity: its linked banner returns, while unrelated unbound manual-off banners stay hidden.
- \think\facade\Db::table('bot_activity_rewards')->where('id', 102)->update(['end_time' => $now + 100]);
- $fresh = new \app\api\lists\BannerLists(['type' => 'pc']);
- check($fresh->count(), 10, 'Activity extension immediately restores eligible banner on the next request');
- check(array_column($fresh->lists(), 'id'), [1, 3], 'Extended activity returns to original order');
- check($list->count(), 9, 'One response keeps count and lists consistent across concurrent changes');
- $GLOBALS['banner_test_now'] = $now + 100;
- $ended = new \app\api\lists\BannerLists(['type' => 'pc']);
- $ended->limitLength = 100;
- check(array_column($ended->lists(), 'id'), [4, 5, 6, 13, 15], 'All bound banners stop exactly at end time while unbound remain');
- \think\facade\Db::table('bot_banner')->where('id', 4)->update(['status' => 0]);
- check((new \app\api\lists\BannerLists(['type' => 'pc']))->count(), 4, 'Manual off takes effect on the next request');
- \think\facade\Db::table('bot_banner')->where('id', 4)->update(['status' => 1]);
- check((new \app\api\lists\BannerLists(['type' => 'pc']))->count(), 5, 'Manual on restores unbound banner');
- echo 'PASS ' . $checks . " banner site assertions\n";
- }
|