mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-21 22:32:41 +01:00
Calendar Import: add unit tests to cover participants
Summary: Add unit tests to easily double-check matched participants in imported calendar events. This will simplify the addition of future features without the risk to break older workflows. Ref T15564 Closes T15905 Closes T15906 Test Plan: See green lights over your new unit tests: arc unit src/applications/calendar/import/__tests__/CalendarImportTestCase.php Reviewers: O1 Blessed Committers, aklapper Reviewed By: O1 Blessed Committers, aklapper Subscribers: aklapper, tobiaswiese, Matthew, Cigaryno Maniphest Tasks: T15906, T15564, T15905 Differential Revision: https://we.phorge.it/D25767
This commit is contained in:
parent
d4d620fa6d
commit
3e53151815
4 changed files with 257 additions and 1 deletions
2
.arclint
2
.arclint
|
@ -65,7 +65,7 @@
|
||||||
"text": {
|
"text": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"(^src/(.*/)?__tests__/[^/]+/.*\\.(txt|json|expect))"
|
"(^src/(.*/)?__tests__/[^/]+/.*\\.(txt|json|expect|ics))"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"text-without-length": {
|
"text-without-length": {
|
||||||
|
|
|
@ -311,6 +311,7 @@ phutil_register_library_map(array(
|
||||||
'BulkSelectParameterType' => 'applications/transactions/bulk/type/BulkSelectParameterType.php',
|
'BulkSelectParameterType' => 'applications/transactions/bulk/type/BulkSelectParameterType.php',
|
||||||
'BulkStringParameterType' => 'applications/transactions/bulk/type/BulkStringParameterType.php',
|
'BulkStringParameterType' => 'applications/transactions/bulk/type/BulkStringParameterType.php',
|
||||||
'BulkTokenizerParameterType' => 'applications/transactions/bulk/type/BulkTokenizerParameterType.php',
|
'BulkTokenizerParameterType' => 'applications/transactions/bulk/type/BulkTokenizerParameterType.php',
|
||||||
|
'CalendarImportTestCase' => 'applications/calendar/import/__tests__/CalendarImportTestCase.php',
|
||||||
'CalendarTimeUtil' => 'applications/calendar/util/CalendarTimeUtil.php',
|
'CalendarTimeUtil' => 'applications/calendar/util/CalendarTimeUtil.php',
|
||||||
'CalendarTimeUtilTestCase' => 'applications/calendar/__tests__/CalendarTimeUtilTestCase.php',
|
'CalendarTimeUtilTestCase' => 'applications/calendar/__tests__/CalendarTimeUtilTestCase.php',
|
||||||
'CelerityAPI' => 'applications/celerity/CelerityAPI.php',
|
'CelerityAPI' => 'applications/celerity/CelerityAPI.php',
|
||||||
|
@ -6318,6 +6319,7 @@ phutil_register_library_map(array(
|
||||||
'BulkSelectParameterType' => 'BulkParameterType',
|
'BulkSelectParameterType' => 'BulkParameterType',
|
||||||
'BulkStringParameterType' => 'BulkParameterType',
|
'BulkStringParameterType' => 'BulkParameterType',
|
||||||
'BulkTokenizerParameterType' => 'BulkParameterType',
|
'BulkTokenizerParameterType' => 'BulkParameterType',
|
||||||
|
'CalendarImportTestCase' => 'PhabricatorTestCase',
|
||||||
'CalendarTimeUtil' => 'Phobject',
|
'CalendarTimeUtil' => 'Phobject',
|
||||||
'CalendarTimeUtilTestCase' => 'PhabricatorTestCase',
|
'CalendarTimeUtilTestCase' => 'PhabricatorTestCase',
|
||||||
'CelerityAPI' => 'Phobject',
|
'CelerityAPI' => 'Phobject',
|
||||||
|
|
|
@ -0,0 +1,231 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class CalendarImportTestCase extends PhabricatorTestCase {
|
||||||
|
|
||||||
|
protected function getPhabricatorTestCaseConfiguration() {
|
||||||
|
return array(
|
||||||
|
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indexes of the "expectedInviteesTests" test.
|
||||||
|
const INVITED_USER = 0;
|
||||||
|
const INVITED_EXPECTED = 1;
|
||||||
|
const INVITED_RESULT = 2;
|
||||||
|
|
||||||
|
public function testIcsFileImportWithGuestThatIsHost() {
|
||||||
|
$alice_unverified =
|
||||||
|
$this->generateTestUserWithVerifiedMail(
|
||||||
|
'alice@example.com',
|
||||||
|
0);
|
||||||
|
$lincoln_verified =
|
||||||
|
$this->generateTestUserWithVerifiedMail(
|
||||||
|
'a.lincoln@example.com',
|
||||||
|
1);
|
||||||
|
$alien_unverified =
|
||||||
|
$this->generateTestUserWithVerifiedMail(
|
||||||
|
'alien.unferified@example.com',
|
||||||
|
0);
|
||||||
|
$alien_verified =
|
||||||
|
$this->generateTestUserWithVerifiedMail(
|
||||||
|
'alien.verified@example.com',
|
||||||
|
1);
|
||||||
|
|
||||||
|
// Tests are event-based. Each event has their expected invitees.
|
||||||
|
$tests = array(
|
||||||
|
// Test zero. Alice imports an event with A.Lincoln.
|
||||||
|
array(
|
||||||
|
'test' => pht('alice invites a.lincoln via verified email'),
|
||||||
|
'file' => 'simple-event-alincoln-guest.ics',
|
||||||
|
'fileAuthor' => $alice_unverified,
|
||||||
|
'expectedInvitees' => 3,
|
||||||
|
'expectedInviteesTests' => array(
|
||||||
|
// Documentation:
|
||||||
|
// Array 0 (INVITED_USER): User object
|
||||||
|
// Array 1 (INVITED_EXPECTED): Presence (bool)
|
||||||
|
array($lincoln_verified, false),
|
||||||
|
array($alice_unverified, false),
|
||||||
|
array($alien_unverified, false),
|
||||||
|
array($alien_verified, false),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Test one. A.Lincoln imports an event with A.Lincoln.
|
||||||
|
array(
|
||||||
|
'test' => pht('a.lincoln self-invite via verified email'),
|
||||||
|
'file' => 'simple-event-alincoln-guest.ics',
|
||||||
|
'fileAuthor' => $lincoln_verified,
|
||||||
|
'expectedInvitees' => 3,
|
||||||
|
'expectedInviteesTests' => array(
|
||||||
|
// array($lincoln_verified, true), // Self-invitation. T15564
|
||||||
|
array($alice_unverified, false),
|
||||||
|
array($alien_unverified, false),
|
||||||
|
array($alien_verified, false),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($tests as $test) {
|
||||||
|
$this->runIcsFileImportTestWithExpectedResults(
|
||||||
|
$test['test'],
|
||||||
|
$test['file'],
|
||||||
|
$test['fileAuthor'],
|
||||||
|
$test['expectedInvitees'],
|
||||||
|
$test['expectedInviteesTests']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function runIcsFileImportTestWithExpectedResults(
|
||||||
|
$test, $file, $importer_author, $expecteds, $invitees_tests) {
|
||||||
|
|
||||||
|
$ics_path = __DIR__.'/events/'.$file;
|
||||||
|
|
||||||
|
// Prepare a calendar import.
|
||||||
|
$import_type = new PhabricatorCalendarICSFileImportEngine();
|
||||||
|
$calendar_import = PhabricatorCalendarImport::initializeNewCalendarImport(
|
||||||
|
$importer_author,
|
||||||
|
clone $import_type);
|
||||||
|
|
||||||
|
// Create the File containing the ICS example.
|
||||||
|
$file_data = Filesystem::readFile($ics_path);
|
||||||
|
$file_test_engine = new PhabricatorTestStorageEngine();
|
||||||
|
$file_params = array(
|
||||||
|
'name' => $file,
|
||||||
|
'viewPolicy' => PhabricatorPolicies::POLICY_USER,
|
||||||
|
'authorPHID' => $importer_author->getPHID(),
|
||||||
|
'storageEngines' => array($file_test_engine),
|
||||||
|
);
|
||||||
|
$file_up = PhabricatorFile::newFromFileData($file_data, $file_params);
|
||||||
|
|
||||||
|
// Create a calendar import with our ICS file.
|
||||||
|
$import_xactions = array();
|
||||||
|
$import_xactions[] = id(new PhabricatorCalendarImportTransaction())
|
||||||
|
->setTransactionType(
|
||||||
|
PhabricatorCalendarImportICSFileTransaction::TRANSACTIONTYPE)
|
||||||
|
->setNewValue($file_up->getPHID());
|
||||||
|
|
||||||
|
// Persist the calendar import and get it.
|
||||||
|
id(new PhabricatorCalendarImportEditor())
|
||||||
|
->setActor($importer_author)
|
||||||
|
->setContentSource($this->newContentSource())
|
||||||
|
->applyTransactions($calendar_import, $import_xactions);
|
||||||
|
|
||||||
|
$import_type->importEventsFromSource(
|
||||||
|
$importer_author,
|
||||||
|
$calendar_import,
|
||||||
|
false);
|
||||||
|
|
||||||
|
// Find imported events from the perspective of the importer author itself.
|
||||||
|
// So we check if we gained some extra people email visibility by mistake.
|
||||||
|
// The backend does not support attachInvitees() and it's done by default.
|
||||||
|
$events = (new PhabricatorCalendarEventQuery())
|
||||||
|
->setViewer($importer_author)
|
||||||
|
->withImportSourcePHIDs(array($calendar_import->getPHID()))
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
// At the moment test cases are hardcoded with one event.
|
||||||
|
$this->assertEqual(
|
||||||
|
1,
|
||||||
|
count($events),
|
||||||
|
pht('Unexpected events in file "%s" on test "%s"',
|
||||||
|
$file,
|
||||||
|
$test));
|
||||||
|
|
||||||
|
// Take the first event.
|
||||||
|
$event = head($events);
|
||||||
|
|
||||||
|
// How many people we invited in this event.
|
||||||
|
$this->assertEqual(
|
||||||
|
$expecteds,
|
||||||
|
count($event->getInvitees()),
|
||||||
|
pht('Unexpected invitees in file "%s" on test "%s"', $file, $test));
|
||||||
|
|
||||||
|
foreach ($invitees_tests as $invitees_test) {
|
||||||
|
$this->assertMatchingInvitees($test, $file, $event, $invitees_tests);
|
||||||
|
}
|
||||||
|
|
||||||
|
$event->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the invitees matches.
|
||||||
|
*/
|
||||||
|
private function assertMatchingInvitees($test, $file, $event, $expecteds) {
|
||||||
|
|
||||||
|
// Index what we expect, by user PHID.
|
||||||
|
$expected_users_by_phid = [];
|
||||||
|
foreach ($expecteds as $expected_invited_data) {
|
||||||
|
$user_phid = $expected_invited_data[self::INVITED_USER]
|
||||||
|
->getPHID();
|
||||||
|
$expected_users_by_phid[$user_phid]
|
||||||
|
= $expected_invited_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current invitees (mixed between "PHID-CXNV" and "PHID-USER").
|
||||||
|
$actuals_phids_mixed = mpull($event->getInvitees(), 'getInviteePHID');
|
||||||
|
|
||||||
|
// Get just actual users.
|
||||||
|
$actual_users = (new PhabricatorUser())->loadAllWhere(
|
||||||
|
'phid IN (%Ls)',
|
||||||
|
$actuals_phids_mixed);
|
||||||
|
$actual_users = mpull($actual_users, null, 'getPHID');
|
||||||
|
|
||||||
|
// Map actual users with the expected ones.
|
||||||
|
foreach ($actual_users as $actual_user) {
|
||||||
|
$user_phid = $actual_user->getPHID();
|
||||||
|
$found = isset($expected_users_by_phid[$user_phid]);
|
||||||
|
if (!$found) {
|
||||||
|
$expected_users_by_phid[$user_phid] = array();
|
||||||
|
}
|
||||||
|
$expected_users_by_phid[$user_phid][self::INVITED_RESULT]
|
||||||
|
= $found;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the future it may be useful to also check external users
|
||||||
|
// by their email. In case, start from here!
|
||||||
|
// but note that the 'getURI()' returns 'mailto:' stuff.
|
||||||
|
// $actual_externals = (new PhabricatorCalendarExternalInvitee())
|
||||||
|
// ->loadAllWhere(
|
||||||
|
// 'phid IN (%Ls)',
|
||||||
|
// $actuals_phids_mixed);
|
||||||
|
// $actual_externals = mpull($actual_externals, null, 'getURI');
|
||||||
|
|
||||||
|
// Check results (matched or not).
|
||||||
|
foreach ($expected_users_by_phid as $phid => $expecteds_data) {
|
||||||
|
$expected = idx($expecteds_data, self::INVITED_EXPECTED, null);
|
||||||
|
$result = idx($expecteds_data, self::INVITED_RESULT, false);
|
||||||
|
$user = idx($expecteds_data, self::INVITED_USER, null);
|
||||||
|
if ($expected !== null) {
|
||||||
|
$this->assertEqual(
|
||||||
|
$expected,
|
||||||
|
$result,
|
||||||
|
pht('Unexpected presence of user "%s" in file "%s" on test "%s"',
|
||||||
|
$user == null ? '(unknown)' : $user->loadPrimaryEmailAddress(),
|
||||||
|
$file,
|
||||||
|
$test));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a test user with a specific verified (or not) email.
|
||||||
|
* @param string $mail Email address
|
||||||
|
* @param int $is_verified 0: unverified, 1: verified
|
||||||
|
* @return PhabricatorUser
|
||||||
|
*/
|
||||||
|
private function generateTestUserWithVerifiedMail($mail, $is_verified) {
|
||||||
|
$user = $this->generateNewTestUser();
|
||||||
|
|
||||||
|
// Set our primary address as verified or not.
|
||||||
|
$email = id(new PhabricatorUserEmail())->loadOneWhere(
|
||||||
|
'userPHID = %s',
|
||||||
|
$user->getPHID());
|
||||||
|
|
||||||
|
$email->setAddress($mail);
|
||||||
|
$email->setIsVerified($is_verified);
|
||||||
|
$email->setIsPrimary(true);
|
||||||
|
$email->save();
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Phacility//Phabricator//EN
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART;VALUE=DATE:20240205
|
||||||
|
DTEND;VALUE=DATE:20240206
|
||||||
|
DTSTAMP:20240411T230207Z
|
||||||
|
ORGANIZER;CN=asd@group.calendar.google.com:mailto:lol@group.calendar.google.com
|
||||||
|
UID:blabla@google.com
|
||||||
|
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=
|
||||||
|
foo@example.com;X-NUM-GUESTS=0:mailto:foo@example.com
|
||||||
|
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=a.
|
||||||
|
lincoln@example.com;X-NUM-GUESTS=0:mailto:a.lincoln@example.com
|
||||||
|
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=
|
||||||
|
alien@example.com;X-NUM-GUESTS=0:mailto:alien@example.com
|
||||||
|
CREATED:20240123T154250Z
|
||||||
|
LAST-MODIFIED:20240123T162620Z
|
||||||
|
SEQUENCE:0
|
||||||
|
STATUS:CONFIRMED
|
||||||
|
SUMMARY:A Lincoln, Foo and Alien to FOSDEM 2024
|
||||||
|
TRANSP:TRANSPARENT
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
Loading…
Reference in a new issue