diff --git a/app/Helper/MWTimestampHelper.php b/app/Helper/MWTimestampHelper.php index a5d03f71e..bab92e404 100644 --- a/app/Helper/MWTimestampHelper.php +++ b/app/Helper/MWTimestampHelper.php @@ -15,8 +15,13 @@ class MWTimestampHelper { private const MWTimestampFormat = 'YmdHis'; public static function getCarbonFromMWTimestamp(string $MWTimestamp): CarbonImmutable { - $carbon = CarbonImmutable::createFromFormat(self::MWTimestampFormat, $MWTimestamp); - if ($carbon === null) { + try { + $carbon = CarbonImmutable::createFromFormat(self::MWTimestampFormat, $MWTimestamp); + } catch (InvalidFormatException $exception) { + throw new InvalidFormatException('Unable to create Carbon object', 0, $exception); + } + + if (!$carbon instanceof CarbonImmutable) { throw new InvalidFormatException('Unable to create Carbon object'); } diff --git a/tests/Helper/MWTimestampHelperTest.php b/tests/Helper/MWTimestampHelperTest.php index ed054cdb4..a82d81f9d 100644 --- a/tests/Helper/MWTimestampHelperTest.php +++ b/tests/Helper/MWTimestampHelperTest.php @@ -19,6 +19,7 @@ public function testGetCarbonFromMWTimestamp() { public function testGetCarbonFromMWTimestampWithInvalidTimestamp() { $this->expectException(InvalidFormatException::class); + $this->expectExceptionMessage('Unable to create Carbon object'); $invalidMwTimestamp = 'invalid_timestamp'; MWTimestampHelper::getCarbonFromMWTimestamp($invalidMwTimestamp); diff --git a/tests/Jobs/PlatformStatsSummaryJobTest.php b/tests/Jobs/PlatformStatsSummaryJobTest.php index 4a954200f..d94e970dc 100644 --- a/tests/Jobs/PlatformStatsSummaryJobTest.php +++ b/tests/Jobs/PlatformStatsSummaryJobTest.php @@ -274,4 +274,50 @@ public function testCreationStats() { ); } + + public function testPrepareStatsTreatsSecondPrecisionTimestampAtThresholdAsActive() { + $currentTime = CarbonImmutable::now(); + + $wiki = Wiki::factory()->create(['deleted_at' => null, 'domain' => 'thresholdtest.com']); + WikiDb::create([ + 'name' => 'mwdb_threshold_' . $wiki->id, + 'user' => 'user', + 'password' => 'password', + 'version' => 'version', + 'prefix' => 'prefix', + 'wiki_id' => $wiki->id, + ]); + + Http::fake([ + $this->mwBackendHost . '/w/api.php?action=query&list=allpages&apnamespace=122&apcontinue=&aplimit=max&format=json' => Http::response([ + 'query' => ['allpages' => []], + ], 200), + $this->mwBackendHost . '/w/api.php?action=query&list=allpages&apnamespace=120&apcontinue=&aplimit=max&format=json' => Http::response([ + 'query' => ['allpages' => []], + ], 200), + ]); + + $job = new PlatformStatsSummaryJob; + (function ($resolver): void { + $this->mwHostResolver = $resolver; + })->call($job, $this->mockMwHostResolver); + + $groups = $job->prepareStats([ + [ + 'wiki' => 'thresholdtest.com', + 'edits' => 1, + 'pages' => 1, + 'users' => 1, + 'active_users' => 1, + 'lastEdit' => MWTimestampHelper::getMWTimestampFromCarbon( + $currentTime->subSeconds(config('wbstack.platform_summary_inactive_threshold')) + ), + 'first100UsingOauth' => '0', + 'platform_summary_version' => 'v1', + ], + ], [$wiki]); + + $this->assertSame(1, $groups['edited_last_90_days']); + $this->assertSame(0, $groups['not_edited_last_90_days']); + } } diff --git a/tests/Jobs/SendEmptyWikiNotificationsJobTest.php b/tests/Jobs/SendEmptyWikiNotificationsJobTest.php index 7fb09ba36..dd07b544c 100644 --- a/tests/Jobs/SendEmptyWikiNotificationsJobTest.php +++ b/tests/Jobs/SendEmptyWikiNotificationsJobTest.php @@ -38,7 +38,7 @@ public function testEmptyWikiNotificationsSendNotification() { Notification::fake(); $user = User::factory()->create(['verified' => true]); $wiki = Wiki::factory()->create(['created_at' => $thresholdDaysAgo]); - $manager = WikiManager::factory()->create(['wiki_id' => $wiki->id, 'user_id' => $user->id]); + WikiManager::factory()->create(['wiki_id' => $wiki->id, 'user_id' => $user->id]); $wiki->wikiLifecycleEvents()->updateOrCreate(['first_edited' => null]); $job = new SendEmptyWikiNotificationsJob; @@ -50,6 +50,24 @@ public function testEmptyWikiNotificationsSendNotification() { ); } + // empty wikis, that are almost old enough (29 days and 23 hrs) + public function testEmptyWikiNotificationsNotSendNotification() { + $thresholdDaysAgo = Carbon::now() + ->subDays((config('wbstack.wiki_empty_notification_threshold') - 1)) + ->subHours(23) + ->toDateTimeString(); + + Notification::fake(); + $user = User::factory()->create(['verified' => true]); + $wiki = Wiki::factory()->create(['created_at' => $thresholdDaysAgo]); + WikiManager::factory()->create(['wiki_id' => $wiki->id, 'user_id' => $user->id]); + $wiki->wikiLifecycleEvents()->updateOrCreate(['first_edited' => null]); + + $job = new SendEmptyWikiNotificationsJob; + $this->assertFalse($job->checkIfWikiIsOldAndEmpty($wiki)); + $job->handle(); + } + // fresh wiki that does not have lifecycle event records yet public function testEmptyWikiNotificationsFreshWiki() { $now = Carbon::now()->toDateTimeString(); diff --git a/tests/Routes/Wiki/ConversionMetricTest.php b/tests/Routes/Wiki/ConversionMetricTest.php index a1c479401..07f031821 100644 --- a/tests/Routes/Wiki/ConversionMetricTest.php +++ b/tests/Routes/Wiki/ConversionMetricTest.php @@ -58,10 +58,10 @@ private function createTestWiki($name, $createdWeeksAgo, $firstEditedWeeksAgo, $ $wiki->created_at = $current_date->subWeeks($createdWeeksAgo); $events = $wiki->wikiLifecycleEvents(); $update = []; - if ($lastEditedWeeksAgo) { + if ($lastEditedWeeksAgo !== null) { $update['last_edited'] = $current_date->subWeeks($lastEditedWeeksAgo); } - if ($firstEditedWeeksAgo) { + if ($firstEditedWeeksAgo !== null) { $update['first_edited'] = $current_date->subWeeks($firstEditedWeeksAgo); } $events->updateOrCreate($update); @@ -134,6 +134,39 @@ public function testDownloadJson() { ); } + public function testDownloadJsonTruncatesFractionalDayDiffs() { + $currentDate = CarbonImmutable::now(); + $createdAt = $currentDate->subDays(200)->subHours(12); // 200.5 days ago + $firstEditedAt = $createdAt->addDays(1)->addHours(12); // 1.5 days after + $lastEditedAt = $currentDate->subDays(100); // 100 days ago + + $wiki = Wiki::factory()->create([ + 'domain' => 'fractional.days.cloud', + 'sitename' => 'Fractional Days Site', + ]); + WikiSiteStats::factory()->create([ + 'wiki_id' => $wiki->id, + 'pages' => 77, + 'activeusers' => 2, + ]); + $wiki->created_at = $createdAt; + $wiki->wikiLifecycleEvents()->updateOrCreate([ + 'first_edited' => $firstEditedAt, + 'last_edited' => $lastEditedAt, + ]); + $wiki->save(); + + $response = $this->getJson($this->route); + + $response->assertStatus(200); + $response->assertJsonFragment([ + 'domain' => 'fractional.days.cloud', + 'time_to_engage_days' => 1, + 'time_before_wiki_abandoned_days' => 100, + 'number_of_active_editors' => 2, + ]); + } + public function testFunctionalWithMissingLifecycleEventsandStats() { $wiki = Wiki::factory()->create([ 'domain' => 'very.new.wikibase.cloud', 'sitename' => 'bsite',