<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[11783] sites/trunk/common/includes/slack/props: Props: Add `#props` messages to w.org profiles.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="http://meta.trac.wordpress.org/changeset/11783">11783</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"http://meta.trac.wordpress.org/changeset/11783","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>iandunn</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2022-04-22 19:55:13 +0000 (Fri, 22 Apr 2022)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Props: Add `#props` messages to w.org profiles.

This helps to give more recogntion for non-code contributions, which we don't track as much as we should.

This won't be enabled until `dotorg/props.php` calls the handler (next week).

See https://github.com/WordPress/five-for-the-future/issues/169</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#sitestrunkcommonincludesslackpropslibphp">sites/trunk/common/includes/slack/props/lib.php</a></li>
<li><a href="#sitestrunkcommonincludesslackpropsteststestlibphp">sites/trunk/common/includes/slack/props/tests/test-lib.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#sitestrunkcommonincludesslackpropstestsvalidrequestjson">sites/trunk/common/includes/slack/props/tests/valid-request.json</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkcommonincludesslackpropslibphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/common/includes/slack/props/lib.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/common/includes/slack/props/lib.php   2022-04-22 19:27:09 UTC (rev 11782)
+++ sites/trunk/common/includes/slack/props/lib.php     2022-04-22 19:55:13 UTC (rev 11783)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2,6 +2,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> namespace Dotorg\Slack\Props;
</span><span class="cx" style="display: block; padding: 0 10px"> use Dotorg\Slack\Send;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use function Dotorg\Profiles\{ post as profiles_post };
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> function show_error( $user ) {
</span><span class="cx" style="display: block; padding: 0 10px">        return "Please use `/props SLACK_USERNAME MESSAGE` to give props.\n";
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -10,6 +11,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px">  * Receive `/props` request and send to `#props`.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * This is being deprecated in favor of `handle_props_message()`.
+ *
</ins><span class="cx" style="display: block; padding: 0 10px">  * @param array $data
</span><span class="cx" style="display: block; padding: 0 10px">  * @param bool  $force_test Send to test channel instead of #props
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -56,3 +59,178 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        return sprintf( "Your props to @%s have been sent.\n", $receiver );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/**
+ * Adds props in Slack to w.org profiles.
+ *
+ * Receives webhook notifications for all new messages in `#props`,
+ */
+function handle_props_message( object $request ) : string {
+       if ( ! is_valid_props( $request->event ) ) {
+               return 'Invalid props';
+       }
+
+       $giver_user          = map_slack_users_to_wporg( array( $request->event->user ) )[0];
+       $recipient_slack_ids = get_recipient_slack_ids( $request->event->blocks );
+
+       if ( empty( $recipient_slack_ids ) ) {
+               return 'Nobody was mentioned';
+       }
+
+       $recipient_users = map_slack_users_to_wporg( $recipient_slack_ids );
+       $recipient_ids   = array_column( $recipient_users, 'id' );
+
+       $url = sprintf(
+               'https://wordpress.slack.com/archives/%s/p%s',
+               $request->event->channel,
+               $request->event->ts
+       );
+       $url = filter_var( $url, FILTER_SANITIZE_URL );
+
+       if ( empty( $recipient_ids ) ) {
+               return 'No recipients';
+       }
+
+       // This is Slack's unintuitive way of giving messages a unique ID :|
+       // https://api.slack.com/messaging/retrieving#individual_messages
+       $message_id = sprintf( '%s-%s', $request->event->channel, $request->event->ts );
+       $message    = prepare_message( $request->event->text, $recipient_users );
+
+       add_activity_to_profile( compact( 'giver_user', 'recipient_ids', 'url', 'message_id', 'message' ) );
+
+       // The request was successful from Slack's perspective as long as we received and validated it. Any errors
+       // that occurred when pushing to Profiles are only significant to us.
+       return 'Success';
+}
+
+/**
+ * Determine if this is an event that we should handle.
+ */
+function is_valid_props( object $event ) : bool {
+       $valid_channels = array(
+               'C0FRG66LR'  #props
+       );
+
+       if ( defined( 'WPORG_SANDBOXED' ) && WPORG_SANDBOXED ) {
+               $valid_channels[] = 'C03AKLN7P9U'; #iandunn-testing
+       }
+
+       $has_required_params = isset( $event->channel, $event->blocks, $event->type ) && is_array( $event->blocks );
+       $in_valid_channel    = in_array( $event->channel, $valid_channels, true );
+
+       $is_correct_type =
+               'message' === $event->type &&
+               empty( $event->subtype ) && // e.g., `message.deleted`, `message.changed`.
+               empty( $event->hidden ) &&
+               empty( $event->thread_ts );
+
+       if ( $is_correct_type && $has_required_params && $in_valid_channel ) {
+               return true;
+       }
+
+       return false;
+}
+
+/**
+ * Parse the mentioned Slack user IDs from a message event.
+ *
+ * This assumes that the app is configured to escape usernames.
+ */
+function get_recipient_slack_ids( array $blocks ) : array {
+       $ids = array();
+
+       foreach ( $blocks as $block ) {
+               foreach ( $block->elements as $element ) {
+                       foreach ( $element->elements as $inner_element ) {
+                               if ( 'user' !== $inner_element->type ) {
+                                       continue;
+                               }
+
+                               $ids[] = $inner_element->user_id;
+                       }
+               }
+       }
+
+       return $ids;
+}
+
+/**
+ * Find the w.org users associated with the given slack accounts.
+ */
+function map_slack_users_to_wporg( array $slack_ids ) : array {
+       global $wpdb;
+
+       if ( empty( $slack_ids ) ) {
+               return array();
+       }
+
+       $wporg_users     = array();
+       $id_placeholders = implode( ', ', array_fill( 0, count( $slack_ids ), '%s' ) );
+
+       $query = $wpdb->prepare( "
+               SELECT
+                       su.slack_id, su.user_id AS wporg_id,
+                       mu.user_login
+               FROM `slack_users` su
+                       JOIN `minibb_users` mu ON su.user_id = mu.ID
+               WHERE `slack_id` IN( $id_placeholders )",
+               $slack_ids
+       );
+
+       $results = $wpdb->get_results( $query, ARRAY_A );
+
+       foreach ( $results as $user ) {
+               $wporg_users[ $user['slack_id'] ] = array(
+                       'id'         => (int) $user['wporg_id'],
+                       'user_login' => $user['user_login'],
+               );
+       }
+
+       return $wporg_users;
+}
+
+/**
+ * Replace Slack IDs with w.org usernames, to better fit w.org profiles.
+ */
+function prepare_message( string $original, array $user_map ) : string {
+       $search  = array();
+       $replace = array();
+
+       foreach ( $user_map as $slack_id => $wporg_user ) {
+               $search[]  = sprintf( '<@%s>', $slack_id );
+               $replace[] = '@' . $wporg_user['user_login'];
+       }
+
+       return str_replace( $search, $replace, $original );
+}
+
+/**
+ * Send a request to Profiles to add the activity.
+ *
+ * See `handle_slack_activity()` in `buddypress.org/.../wporg-profiles-activity-handler.php` for the needed args.
+ */
+function add_activity_to_profile( array $request_args ) : bool {
+       require_once dirname( __DIR__, 2 ) . '/profiles/profiles.php';
+
+       $request_args = array_merge(
+               $request_args,
+               array(
+                       'action'   => 'wporg_handle_activity',
+                       'source'   => 'slack',
+                       'activity' => "props_given",
+               )
+       );
+
+       $response_body = profiles_post( $request_args );
+
+       if ( is_numeric( $response_body ) && (int) $response_body > 0 ) {
+               $success = true;
+
+       } else {
+               $success = false;
+
+               trigger_error( 'Adding activity failed with error: ' . $response_body, E_USER_WARNING );
+       }
+
+       return $success;
+}
</ins></span></pre></div>
<a id="sitestrunkcommonincludesslackpropsteststestlibphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/common/includes/slack/props/tests/test-lib.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/common/includes/slack/props/tests/test-lib.php        2022-04-22 19:27:09 UTC (rev 11782)
+++ sites/trunk/common/includes/slack/props/tests/test-lib.php  2022-04-22 19:55:13 UTC (rev 11783)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1,18 +1,25 @@
</span><span class="cx" style="display: block; padding: 0 10px"> <?php
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> namespace Dotorg\Slack\Props\Tests;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use wpdbStub;
</ins><span class="cx" style="display: block; padding: 0 10px"> use PHPUnit\Framework\TestCase;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-use function Dotorg\Slack\Props\{ run };
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use function Dotorg\Slack\Props\{ run, is_valid_props, get_recipient_slack_ids, map_slack_users_to_wporg, prepare_message };
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px">  * @group slack
</span><span class="cx" style="display: block; padding: 0 10px">  * @group props
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-class Test_Props extends TestCase {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+class Test_Props_Lib extends TestCase {
</ins><span class="cx" style="display: block; padding: 0 10px">         public static function setUpBeforeClass() : void {
</span><span class="cx" style="display: block; padding: 0 10px">                require_once dirname( __DIR__ ) . '/lib.php';
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        protected static function get_valid_request() {
+               $json = file_get_contents( __DIR__ . '/valid-request.json' );
+
+               return json_decode( $json );
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         /**
</span><span class="cx" style="display: block; padding: 0 10px">         * @covers ::run
</span><span class="cx" style="display: block; padding: 0 10px">         * @dataProvider data_run
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -89,4 +96,279 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                return $cases;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * @covers ::is_valid_props
+        * @dataProvider data_is_valid_props
+        * @group unit
+        */
+       public function test_is_valid_props( object $event, bool $expected ) : void {
+               $actual = is_valid_props( $event );
+
+               $this->assertSame( $expected, $actual );
+       }
+
+       public function data_is_valid_props() : array {
+               $valid_request = self::get_valid_request();
+
+               $wrong_channel_event = json_decode( json_encode( $valid_request->event ) );
+               $wrong_channel_event->channel = 'C01234567';
+
+               $reaction_event = json_decode( json_encode( $valid_request->event ) );
+               $reaction_event->type = 'reaction_added';
+
+               $deleted_event = json_decode( json_encode( $valid_request->event ) );
+               $deleted_event->subtype = 'message_deleted';
+
+               $hidden_event = json_decode( json_encode( $valid_request->event ) );
+               $hidden_event->hidden = true;
+
+               $thread_event = json_decode( json_encode( $valid_request->event ) );
+               $thread_event->thread_ts = $valid_request->event->ts;
+
+               $cases = array(
+                       'missing critical properties' => array(
+                               'request'  => (object) array( 'foo' => 'bar' ),
+                               'expected' => false,
+                       ),
+
+                       'wrong channel' => array(
+                               'request'  => $wrong_channel_event,
+                               'expected' => false,
+                       ),
+
+                       'wrong type' => array(
+                               'request'  => $reaction_event,
+                               'expected' => false,
+                       ),
+
+                       'wrong subtype' => array(
+                               'request'  => $deleted_event,
+                               'expected' => false,
+                       ),
+
+                       'hidden' => array(
+                               'request'  => $hidden_event,
+                               'expected' => false,
+                       ),
+
+                       'reply in thread' => array(
+                               'request'  => $thread_event,
+                               'expected' => false,
+                       ),
+
+                       'valid event' => array(
+                               'request'  => $valid_request->event,
+                               'expected' => true,
+                       ),
+               );
+
+               return $cases;
+       }
+
+       /**
+        * @covers ::get_recipient_slack_ids
+        * @dataProvider data_get_recipient_slack_ids
+        * @group unit
+        */
+       public function test_get_recipient_slack_ids( array $blocks, array $expected ) : void {
+               $actual = get_recipient_slack_ids( $blocks );
+
+               $this->assertSame( $expected, $actual );
+       }
+
+       public function data_get_recipient_slack_ids() : array {
+               $valid_request = self::get_valid_request();
+
+               $cases = array(
+                       'empty' => array(
+                               'blocks'   => array(),
+                               'expected' => array(),
+                       ),
+
+                       'valid' => array(
+                               'blocks'   => $valid_request->event->blocks,
+                               'expected' => array(
+                                       'U02RR6SGY',
+                                       'U02RQHNND',
+                                       'U3KJ0TK4L',
+                                       'U4L99HZB6',
+                                       'U024MFP4L',
+                                       'U6R2E3Y9Y',
+                                       'U023GFZJ07L',
+                                       'U1E5RLU1L',
+                               ),
+                       ),
+               );
+
+               return $cases;
+       }
+
+       /**
+        * @covers ::map_slack_users_to_wporg
+        * @dataProvider data_map_slack_users_to_wporg
+        * @group unit
+        */
+       public function test_map_slack_users_to_wporg( array $slack_ids, array $db_results, array $expected ) : void {
+               global $wpdb;
+
+               $wpdb = $this->createStub( wpdbStub::class );
+               $wpdb->method( 'get_results' )->willReturn( $db_results );
+
+               $actual = map_slack_users_to_wporg( $slack_ids );
+
+               $this->assertSame( $expected, $actual );
+       }
+
+       public function data_map_slack_users_to_wporg() : array {
+               $cases = array(
+                       'empty' => array(
+                               'slack_ids'  => array(),
+                               'db_results' => array(),
+                               'expected'   => array(),
+                       ),
+
+                       'valid giver' => array(
+                               'slack_ids' => array( 'U02QCF502' ),
+
+                               'db_results' => array(
+                                       array(
+                                               'slack_id'   => 'U02QCF502',
+                                               'wporg_id'   => '33690',
+                                               'user_login' => 'iandunn',
+                                       ),
+                               ),
+
+                               'expected' => array(
+                                       'U02QCF502' => array(
+                                               'id'         => 33690,
+                                               'user_login' => 'iandunn',
+                                       ),
+                               ),
+                       ),
+
+                       'valid receivers' => array(
+                               'slack_ids' => array( 'U02RQHNND', 'U02RR6SGY', 'U3KJ0TK4L', 'U4L99HZB6' ),
+
+                               'db_results' => array(
+                                       array(
+                                               'slack_id'   => 'U02RQHNND',
+                                               'wporg_id'   => '297445',
+                                               'user_login' => 'SergeyBiryukov',
+                                       ),
+
+                                       array(
+                                               'slack_id'   => 'U02RR6SGY',
+                                               'wporg_id'   => '2255796',
+                                               'user_login' => 'Mamaduka',
+                                       ),
+
+
+                                       array(
+                                               'slack_id'   => 'U3KJ0TK4L',
+                                               'wporg_id'   => '15049054',
+                                               'user_login' => 'davidbaumwald',
+                                       ),
+
+                                       array(
+                                               'slack_id'   => 'U4L99HZB6',
+                                               'wporg_id'   => '8976791',
+                                               'user_login' => 'pbiron',
+                                       ),
+                               ),
+
+                               'expected' => array(
+                                       'U02RQHNND' => array(
+                                               'id'         => 297445,
+                                               'user_login' => 'SergeyBiryukov',
+                                       ),
+                                       'U02RR6SGY' => array(
+                                               'id'         => 2255796,
+                                               'user_login' => 'Mamaduka',
+                                       ),
+                                       'U3KJ0TK4L' => array(
+                                               'id'         => 15049054,
+                                               'user_login' => 'davidbaumwald',
+                                       ),
+
+                                       'U4L99HZB6' => array(
+                                               'id'         => 8976791,
+                                               'user_login' => 'pbiron',
+                                       ),
+                               ),
+                       ),
+               );
+
+               return $cases;
+       }
+
+       /**
+        * @covers ::prepare_message
+        * @dataProvider data_prepare_message
+        * @group unit
+        */
+       public function test_prepare_message( string $text, array $user_map, string $expected ) : void {
+               $actual = prepare_message( $text, $user_map );
+
+               $this->assertSame( $expected, $actual );
+       }
+
+       public function data_prepare_message() : array {
+               $valid_request = self::get_valid_request();
+
+               $cases = array(
+                       'empty' => array(
+                               'text'     => '',
+                               'user_map' => array(),
+                               'expected' => '',
+                       ),
+
+                       'valid' => array(
+                               'text' => $valid_request->event->text,
+                               'user_map' => array(
+                                       'U023GFZJ07L' => array(
+                                               'id' => 18752239,
+                                               'user_login' => 'costdev',
+                                       ),
+                                       'U024MFP4L' => array(
+                                               'id' => 2545,
+                                               'user_login' => 'markjaquith',
+                                       ),
+
+                                       'U02RQHNND' => array(
+                                               'id' => 297445,
+                                               'user_login' => 'SergeyBiryukov',
+                                       ),
+
+                                       'U02RR6SGY' => array(
+                                               'id' => 2255796,
+                                               'user_login' => 'Mamaduka',
+                                       ),
+
+                                       'U1E5RLU1L' => array(
+                                               'id' => 15152479,
+                                               'user_login' => 'jeroenrotty',
+                                       ),
+
+                                       'U3KJ0TK4L' => array(
+                                               'id' => 15049054,
+                                               'user_login' => 'davidbaumwald',
+                                       ),
+
+                                       'U4L99HZB6' => array(
+                                               'id' => 8976791,
+                                               'user_login' => 'pbiron',
+                                       ),
+
+                                       'U6R2E3Y9Y' => array(
+                                               'id' => 15524609,
+                                               'user_login' => 'webcommsat',
+                                       ),
+                               ),
+                               'expected' => 'props to @Mamaduka for co-leading 5.9.3 RC 1, to @SergeyBiryukov for running mission control and to @davidbaumwald @pbiron @markjaquith @webcommsat @costdev @jeroenrotty for their help testing the release package :community: :wordpress:',
+                       ),
+               );
+
+               return $cases;
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="sitestrunkcommonincludesslackpropstestsvalidrequestjson"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/common/includes/slack/props/tests/valid-request.json</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/common/includes/slack/props/tests/valid-request.json                          (rev 0)
+++ sites/trunk/common/includes/slack/props/tests/valid-request.json    2022-04-22 19:55:13 UTC (rev 11783)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,123 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+{
+       "token": "mock token value",
+       "team_id": "mock team ID",
+       "api_app_id": "mock app ID",
+       "event": {
+               "client_msg_id": "mock client message ID",
+               "type": "message",
+               "text": "props to <@U02RR6SGY> for co-leading 5.9.3 RC 1, to <@U02RQHNND> for running mission control and to <@U3KJ0TK4L> <@U4L99HZB6> <@U024MFP4L> <@U6R2E3Y9Y> <@U023GFZJ07L> <@U1E5RLU1L> for their help testing the release package :community: :wordpress:",
+               "user": "mock user ID",
+               "ts": "1649883341.182409",
+               "team": "mock team ID",
+               "blocks": [
+                       {
+                               "type": "rich_text",
+                               "block_id": "9i6W",
+                               "elements": [
+                                       {
+                                               "type": "rich_text_section",
+                                               "elements": [
+                                                       {
+                                                               "type": "text",
+                                                               "text": "props to "
+                                                       },
+                                                       {
+                                                               "type": "user",
+                                                               "user_id": "U02RR6SGY"
+                                                       },
+                                                       {
+                                                               "type": "text",
+                                                               "text": " for co-leading 5.9.3 RC 1, to "
+                                                       },
+                                                       {
+                                                               "type": "user",
+                                                               "user_id": "U02RQHNND"
+                                                       },
+                                                       {
+                                                               "type": "text",
+                                                               "text": " for running mission control and to "
+                                                       },
+                                                       {
+                                                               "type": "user",
+                                                               "user_id": "U3KJ0TK4L"
+                                                       },
+                                                       {
+                                                               "type": "text",
+                                                               "text": " "
+                                                       },
+                                                       {
+                                                               "type": "user",
+                                                               "user_id": "U4L99HZB6"
+                                                       },
+                                                       {
+                                                               "type": "text",
+                                                               "text": " "
+                                                       },
+                                                       {
+                                                               "type": "user",
+                                                               "user_id": "U024MFP4L"
+                                                       },
+                                                       {
+                                                               "type": "text",
+                                                               "text": " "
+                                                       },
+                                                       {
+                                                               "type": "user",
+                                                               "user_id": "U6R2E3Y9Y"
+                                                       },
+                                                       {
+                                                               "type": "text",
+                                                               "text": " "
+                                                       },
+                                                       {
+                                                               "type": "user",
+                                                               "user_id": "U023GFZJ07L"
+                                                       },
+                                                       {
+                                                               "type": "text",
+                                                               "text": " "
+                                                       },
+                                                       {
+                                                               "type": "user",
+                                                               "user_id": "U1E5RLU1L"
+                                                       },
+                                                       {
+                                                               "type": "text",
+                                                               "text": " for their help testing the release package "
+                                                       },
+                                                       {
+                                                               "type": "emoji",
+                                                               "name": "community"
+                                                       },
+                                                       {
+                                                               "type": "text",
+                                                               "text": " "
+                                                       },
+                                                       {
+                                                               "type": "emoji",
+                                                               "name": "wordpress"
+                                                       }
+                                               ]
+                                       }
+                               ]
+                       }
+               ],
+               "channel": "C0FRG66LR",
+               "event_ts": "1649883341.182409",
+               "channel_type": "group"
+       },
+       "type": "event_callback",
+       "event_id": "mock event ID",
+       "event_time": 1649883341,
+       "authorizations": [
+               {
+                       "enterprise_id": null,
+                       "team_id": "mock team ID",
+                       "user_id": "mock user ID",
+                       "is_bot": true,
+                       "is_enterprise_install": false
+               }
+       ],
+       "is_ext_shared_channel": false,
+       "event_context": "mock event context"
+}
</ins></span></pre>
</div>
</div>

</body>
</html>