{"action":"created","comment":{"url":"https://api.github.com/repos/matrix-org/sytest/pulls/comments/41402199","id":41402199,"diff_hunk":"@@ -107,3 +111,424 @@\n          Future->done(1);\n       });\n    };\n+\n+test \"Can invite existing 3pid\",\n+   requires => [qw( user more_users test_http_server_hostandport await_http_request )],\n+\n+   do => sub {\n+      my ( $inviter, $more_users, $id_server, $await_http_request ) = @_;\n+\n+      my $invitee_email = \"marmosets\\@monkeyworld.org\";\n+      my $invitee_mxid = $more_users->[0]->user_id;\n+      my $room_id;\n+\n+      Future->needs_all(\n+         stub_is_lookup($invitee_email, $invitee_mxid, $await_http_request),\n+\n+         matrix_create_and_join_room( [ $inviter ], visibility => \"private\" )\n+         ->then( sub {\n+            ( $room_id ) = @_;\n+            do_request_json_for( $inviter,\n+               method => \"POST\",\n+               uri    => \"/api/v1/rooms/$room_id/invite\",\n+\n+               content => {\n+                  id_server => $id_server,\n+                  medium => \"email\",\n+                  address => $invitee_email,\n+                  display_name => \"Cute things\",\n+               },\n+            ),\n+         })->then( sub {\n+            matrix_get_room_state( $inviter, $room_id,\n+               type => \"m.room.member\",\n+               state_key => $invitee_mxid,\n+            )->on_done( sub {\n+               my ( $body ) = @_;\n+\n+               log_if_fail \"Body\", $body;\n+               $body->{membership} eq \"invite\" or\n+                  die \"Expected invited user membership to be 'invite'\";\n+            });\n+         }),\n+      );\n+   };\n+\n+test \"Can invite unbound 3pid\",\n+   requires => [qw( user more_users test_http_server_hostandport await_http_request first_home_server synapse_client_locations )],\n+   do => sub {\n+      my ( $user, $users, $id_server, $await_http_request, $user_agent ) = @_;\n+      can_invite_unbound_3pid( $user, $users, $id_server, $await_http_request, $user_agent );\n+   };\n+\n+test \"Can invite unbound 3pid over federation\",\n+   requires => [qw( user remote_users test_http_server_hostandport await_http_request synapse_client_locations )],\n+   do => sub {\n+      my ( $user, $users, $id_server, $await_http_request, undef, $user_agent ) = @_;\n+      can_invite_unbound_3pid( $user, $users, $id_server, $await_http_request, $user_agent );\n+   };\n+\n+sub can_invite_unbound_3pid {\n+   my ( $inviter, $other_users, $id_server, $await_http_request, $user_agent ) = @_;\n+   my $invitee = $other_users->[0];\n+\n+   make_3pid_invite( $inviter, $invitee, $id_server, $await_http_request, 1, sub {\n+      my ( $token, $public_key, $signature, $room_id ) = @_;\n+\n+      do_request_json_for( $invitee,\n+         method => \"POST\",\n+         uri    => \"/api/v1/rooms/$room_id/join\",\n+         content => {\n+            token => $token,\n+            public_key => encode_base64_unpadded( $public_key ),\n+            signature => $signature,\n+            key_validity_url => \"https://$id_server/_matrix/identity/api/v1/pubkey/isvalid\",\n+            sender => $inviter->user_id,\n+         }\n+      );\n+   },\n+   [ stub_is_key_validation( JSON::true, $await_http_request, $user_agent ) ] );\n+};\n+\n+test \"3pid invite join with wrong signature are rejected\",\n+   requires => [qw( user more_users test_http_server_hostandport await_http_request )],\n+   do => sub {\n+      my ( $user, $other_users, $id_server, $await_http_request ) = @_;\n+      my $invitee = $other_users->[0];\n+\n+      make_3pid_invite( $user, $invitee, $id_server, $await_http_request, 0, sub {\n+         my ( $token, $public_key, $signature, $room_id ) = @_;\n+\n+         do_request_json_for( $invitee,\n+            method => \"POST\",\n+            uri    => \"/api/v1/rooms/$room_id/join\",\n+            content => {\n+               token => $token,\n+               public_key => encode_base64_unpadded( $public_key ),\n+               signature => \"abc\",\n+               key_validity_url => \"https://$id_server/_matrix/identity/api/v1/pubkey/isvalid\",\n+               sender => $user->user_id,\n+            }\n+         );\n+      },\n+      # This should really be an optional stub\n+      #[ stub_is_key_validation( JSON::true, $await_http_request ) ],\n+      [],\n+      );\n+   };\n+\n+test \"3pid invite join with missing signature are rejected\",\n+   requires => [qw( user more_users test_http_server_hostandport await_http_request )],\n+   do => sub {\n+      my ( $user, $other_users, $id_server, $await_http_request ) = @_;\n+      my $invitee = $other_users->[0];\n+\n+      make_3pid_invite( $user, $invitee, $id_server, $await_http_request, 0, sub {\n+         my ( $token, $public_key, $signature, $room_id ) = @_;\n+\n+         do_request_json_for( $invitee,\n+            method => \"POST\",\n+            uri    => \"/api/v1/rooms/$room_id/join\",\n+            content => {\n+               token => $token,\n+               public_key => encode_base64_unpadded( $public_key ),\n+               key_validity_url => \"https://$id_server/_matrix/identity/api/v1/pubkey/isvalid\",\n+               sender => $user->user_id,\n+            }\n+         );\n+      },\n+      [],\n+      );\n+   };\n+\n+test \"3pid invite join with wrong key_validity_url are rejected\",\n+   requires => [qw( user more_users test_http_server_hostandport await_http_request )],\n+   do => sub {\n+      my ( $user, $other_users, $id_server, $await_http_request ) = @_;\n+      my $invitee = $other_users->[0];\n+\n+      make_3pid_invite( $user, $invitee, $id_server, $await_http_request, 0, sub {\n+         my ( $token, $public_key, $signature, $room_id ) = @_;\n+\n+         do_request_json_for( $invitee,\n+            method => \"POST\",\n+            uri    => \"/api/v1/rooms/$room_id/join\",\n+            content => {\n+               token => $token,\n+               public_key => encode_base64_unpadded( $public_key ),\n+               signature => $signature,\n+               key_validity_url => \"https://wrongdoesnotexist$id_server/_matrix/identity/api/v1/pubkey/isvalid\",\n+               sender => $user->user_id,\n+            }\n+         );\n+      },\n+      [],\n+      );\n+   };\n+\n+test \"3pid invite join with missing key_validity_url are rejected\",\n+   requires => [qw( user more_users test_http_server_hostandport await_http_request )],\n+   do => sub {\n+      my ( $user, $other_users, $id_server, $await_http_request ) = @_;\n+      my $invitee = $other_users->[0];\n+\n+      make_3pid_invite( $user, $invitee, $id_server, $await_http_request, 0, sub {\n+         my ( $token, $public_key, $signature, $room_id ) = @_;\n+\n+         do_request_json_for( $invitee,\n+            method => \"POST\",\n+            uri    => \"/api/v1/rooms/$room_id/join\",\n+            content => {\n+               token => $token,\n+               public_key => encode_base64_unpadded( $public_key ),\n+               signature => $signature,\n+               sender => $user->user_id,\n+            }\n+         );\n+      },\n+      [],\n+      );\n+   };\n+\n+test \"3pid invite join with wrong signature are rejected\",\n+   requires => [qw( user more_users test_http_server_hostandport await_http_request )],\n+   do => sub {\n+      my ( $user, $other_users, $id_server, $await_http_request ) = @_;\n+      my $invitee = $other_users->[0];\n+\n+      make_3pid_invite( $user, $invitee, $id_server, $await_http_request, 0, sub {\n+         my ( $token, $public_key, $signature, $room_id ) = @_;\n+\n+         my ( $wrong_public_key, $wrong_private_key ) = $crypto_sign->keypair;\n+\n+         do_request_json_for( $invitee,\n+            method => \"POST\",\n+            uri    => \"/api/v1/rooms/$room_id/join\",\n+            content => {\n+               token => $token,\n+               public_key => encode_base64_unpadded( $wrong_public_key ),\n+               signature => encode_base64_unpadded( $crypto_sign->mac( $token, $wrong_private_key ) ),\n+               key_validity_url => \"https://$id_server/_matrix/identity/api/v1/pubkey/isvalid\",\n+               sender => $user->user_id,\n+            }\n+         );\n+      },\n+      [],\n+      );\n+   };\n+\n+test \"3pid invite join fails if key revoked\",\n+   requires => [qw( user more_users test_http_server_hostandport await_http_request )],\n+   do => sub {\n+      my ( $inviter, $other_users, $id_server, $await_http_request ) = @_;\n+      my $invitee = $other_users->[0];\n+\n+      make_3pid_invite ($inviter, $invitee, $id_server, $await_http_request, 0, sub {\n+         my ( $token, $public_key, $signature, $room_id ) = @_;\n+\n+         do_request_json_for( $invitee,\n+            method => \"POST\",\n+            uri    => \"/api/v1/rooms/$room_id/join\",\n+            content => {\n+               token => $token,\n+               public_key => encode_base64_unpadded( $public_key ),\n+               signature => $signature,\n+               key_validity_url => \"https://$id_server/_matrix/identity/api/v1/pubkey/isvalid\",\n+               sender => $inviter->user_id,\n+            }\n+         );\n+      },\n+      [ stub_is_key_validation( JSON::false, $await_http_request ) ],\n+      );\n+   };\n+\n+# TODO: Work out how to require an id_server which only listens for one request then closes the socket\n+#test \"3pid invite join fails if keyserver cannot be reached\",\n+#   requires => [qw( user more_users test_http_server_hostandport await_http_request )],\n+#   do => sub {\n+#      my ( $user, $other_users, $id_server, $make_test_room, $await_http_request ) = @_;\n+#\n+#      my $non_existent_id_server = \"ireallyhopethishostdoesnotexist\";\n+#      my $invitee_email = 'lemurs@monkeyworld.org';\n+#      my $inviter = $user;\n+#      my $invitee = $other_users->[0];\n+#\n+#      my $token = \"abc123\";\n+#\n+#      my ( $public_key, $private_key ) = $crypto_sign->keypair;\n+#      my $encoded_public_key = encode_base64_unpadded( $public_key );\n+#      my $signature = encode_base64_unpadded( $crypto_sign->mac( $token, $private_key ) );\n+#\n+#      Future->needs_all(\n+#         stub_is_lookup( $invitee_email, undef, $await_http_request ),\n+#\n+#         stub_is_token_generation( $token, $encoded_public_key, $await_http_request ),\n+#\n+#         matrix_create_room->( $inviter, visibility => \"private\" )\n+#         ->then(sub {\n+#            my ( $room_id ) = @_;\n+#            do_3pid_invite( $room_id, $id_server, $invitee_email, $do_request_json )\n+#            ->then( sub {\n+#               do_request_json_for( $invitee,\n+#                  method => \"POST\",\n+#                  uri    => \"/api/v1/rooms/$room_id/join\",\n+#                  content => {\n+#                     token => $token,\n+#                     public_key => encode_base64_unpadded( $public_key ),\n+#                     signature => $signature,\n+#                     key_validity_url => \"https://$id_server/_matrix/identity/api/v1/pubkey/isvalid\",\n+#                     sender => $user->user_id,\n+#                  }\n+#               )\n+#               ->followed_by(\\&main::expect_http_4xx)\n+#               ->then( sub {\n+#                  $do_request_json->(\n+#                     method => \"GET\",\n+#                     uri    => \"/api/v1/rooms/$room_id/state/m.room.member/\".$invitee->user_id,\n+#                  )->followed_by(assert_membership( $do_request_json, $inviter, undef ) )\n+#               })\n+#            })\n+#         }),\n+#      );\n+#   };\n+\n+# In order:\n+#  1. Creates all state needed to issue a 3pid invite\n+#  2. Issues the invite\n+#  3. Calls join_sub with the following args: token (str), public_key (base64 str), $signature (base64 str), room_id (str)\n+#  4. Asserts that invitee did/didn't join the room, depending on truthiness of expect_join_success\n+#  5. Awaits on all passed futures, so that you can stub/mock things as you wish\n+sub make_3pid_invite {\n+   my ( $inviter, $invitee, $id_server, $await_http_request, $expect_join_success, $join_sub, $futures ) = @_;\n+\n+   my $invitee_email = 'lemurs@monkeyworld.org';\n+\n+   my $token = \"abc123\";\n+\n+   my ( $public_key, $private_key ) = $crypto_sign->keypair;\n+   my $encoded_public_key = encode_base64_unpadded( $public_key );\n+   my $signature = encode_base64_unpadded( $crypto_sign->mac( $token, $private_key ) );\n+\n+   my $response_verifier = $expect_join_success\n+      ? sub {\n+         my ( $f ) = @_;\n+         $f->then( sub {\n+            Future->done( @_ );\n+         }, sub {\n+            my ( undef, $name, $response ) = @_;\n+\n+            Future->fail( @_ );\n+         });\n+      }\n+      : \\&main::expect_http_4xx;\n+\n+   Future->needs_all(\n+      stub_is_lookup( $invitee_email, undef, $await_http_request ),\n+\n+      stub_is_token_generation( $token, $encoded_public_key, $await_http_request ),\n+\n+      @$futures,\n+\n+      matrix_create_room( $inviter, visibility => \"private\" )\n+      ->then(sub {\n+         my ( $room_id ) = @_;\n+         do_3pid_invite( $inviter, $room_id, $id_server, $invitee_email )\n+         ->then( sub {\n+            $join_sub->( $token, $public_key, $signature, $room_id )\n+            ->followed_by($response_verifier)\n+            ->then( sub {\n+               matrix_get_room_state( $inviter, $room_id,","path":"tests/30rooms/06invite.pl","position":342,"original_position":342,"commit_id":"310b42b394729ede3f23ca02ee472e4ebe2da6c8","original_commit_id":"310b42b394729ede3f23ca02ee472e4ebe2da6c8","user":{"login":"leonerd","id":350941,"avatar_url":"https://avatars.githubusercontent.com/u/350941?v=3","gravatar_id":"","url":"https://api.github.com/users/leonerd","html_url":"https://github.com/leonerd","followers_url":"https://api.github.com/users/leonerd/followers","following_url":"https://api.github.com/users/leonerd/following{/other_user}","gists_url":"https://api.github.com/users/leonerd/gists{/gist_id}","starred_url":"https://api.github.com/users/leonerd/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/leonerd/subscriptions","organizations_url":"https://api.github.com/users/leonerd/orgs","repos_url":"https://api.github.com/users/leonerd/repos","events_url":"https://api.github.com/users/leonerd/events{/privacy}","received_events_url":"https://api.github.com/users/leonerd/received_events","type":"User","site_admin":false},"body":"Most of this nesting doesn't really seem to be required. Furthermore, if you pulled out `my $room_id;` to above the `->needs_all` invocation, then the whole lot could become flat rather than pyramid-shaped, thus saving indentation levels.","created_at":"2015-10-07T15:10:06Z","updated_at":"2015-10-07T15:10:06Z","html_url":"https://github.com/matrix-org/sytest/pull/18#discussion_r41402199","pull_request_url":"https://api.github.com/repos/matrix-org/sytest/pulls/18","_links":{"self":{"href":"https://api.github.com/repos/matrix-org/sytest/pulls/comments/41402199"},"html":{"href":"https://github.com/matrix-org/sytest/pull/18#discussion_r41402199"},"pull_request":{"href":"https://api.github.com/repos/matrix-org/sytest/pulls/18"}}},"pull_request":{"url":"https://api.github.com/repos/matrix-org/sytest/pulls/18","id":46856772,"html_url":"https://github.com/matrix-org/sytest/pull/18","diff_url":"https://github.com/matrix-org/sytest/pull/18.diff","patch_url":"https://github.com/matrix-org/sytest/pull/18.patch","issue_url":"https://api.github.com/repos/matrix-org/sytest/issues/18","number":18,"state":"open","locked":false,"title":"Add tests for 3pid invites","user":{"login":"illicitonion","id":1131704,"avatar_url":"https://avatars.githubusercontent.com/u/1131704?v=3","gravatar_id":"","url":"https://api.github.com/users/illicitonion","html_url":"https://github.com/illicitonion","followers_url":"https://api.github.com/users/illicitonion/followers","following_url":"https://api.github.com/users/illicitonion/following{/other_user}","gists_url":"https://api.github.com/users/illicitonion/gists{/gist_id}","starred_url":"https://api.github.com/users/illicitonion/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/illicitonion/subscriptions","organizations_url":"https://api.github.com/users/illicitonion/orgs","repos_url":"https://api.github.com/users/illicitonion/repos","events_url":"https://api.github.com/users/illicitonion/events{/privacy}","received_events_url":"https://api.github.com/users/illicitonion/received_events","type":"User","site_admin":false},"body":"The spec is still under review, but might as well get the \"you're perling wrong\" comments out of the way :)","created_at":"2015-10-06T02:31:32Z","updated_at":"2015-10-07T15:10:06Z","closed_at":null,"merged_at":null,"merge_commit_sha":"dfd78db3dc05e4d96c3f2eaa1983e86c2ba51100","assignee":{"login":"leonerd","id":350941,"avatar_url":"https://avatars.githubusercontent.com/u/350941?v=3","gravatar_id":"","url":"https://api.github.com/users/leonerd","html_url":"https://github.com/leonerd","followers_url":"https://api.github.com/users/leonerd/followers","following_url":"https://api.github.com/users/leonerd/following{/other_user}","gists_url":"https://api.github.com/users/leonerd/gists{/gist_id}","starred_url":"https://api.github.com/users/leonerd/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/leonerd/subscriptions","organizations_url":"https://api.github.com/users/leonerd/orgs","repos_url":"https://api.github.com/users/leonerd/repos","events_url":"https://api.github.com/users/leonerd/events{/privacy}","received_events_url":"https://api.github.com/users/leonerd/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/matrix-org/sytest/pulls/18/commits","review_comments_url":"https://api.github.com/repos/matrix-org/sytest/pulls/18/comments","review_comment_url":"https://api.github.com/repos/matrix-org/sytest/pulls/comments{/number}","comments_url":"https://api.github.com/repos/matrix-org/sytest/issues/18/comments","statuses_url":"https://api.github.com/repos/matrix-org/sytest/statuses/310b42b394729ede3f23ca02ee472e4ebe2da6c8","head":{"label":"matrix-org:daniel/3pidinvites","ref":"daniel/3pidinvites","sha":"310b42b394729ede3f23ca02ee472e4ebe2da6c8","user":{"login":"matrix-org","id":8418310,"avatar_url":"https://avatars.githubusercontent.com/u/8418310?v=3","gravatar_id":"","url":"https://api.github.com/users/matrix-org","html_url":"https://github.com/matrix-org","followers_url":"https://api.github.com/users/matrix-org/followers","following_url":"https://api.github.com/users/matrix-org/following{/other_user}","gists_url":"https://api.github.com/users/matrix-org/gists{/gist_id}","starred_url":"https://api.github.com/users/matrix-org/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/matrix-org/subscriptions","organizations_url":"https://api.github.com/users/matrix-org/orgs","repos_url":"https://api.github.com/users/matrix-org/repos","events_url":"https://api.github.com/users/matrix-org/events{/privacy}","received_events_url":"https://api.github.com/users/matrix-org/received_events","type":"Organization","site_admin":false},"repo":{"id":23436169,"name":"sytest","full_name":"matrix-org/sytest","owner":{"login":"matrix-org","id":8418310,"avatar_url":"https://avatars.githubusercontent.com/u/8418310?v=3","gravatar_id":"","url":"https://api.github.com/users/matrix-org","html_url":"https://github.com/matrix-org","followers_url":"https://api.github.com/users/matrix-org/followers","following_url":"https://api.github.com/users/matrix-org/following{/other_user}","gists_url":"https://api.github.com/users/matrix-org/gists{/gist_id}","starred_url":"https://api.github.com/users/matrix-org/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/matrix-org/subscriptions","organizations_url":"https://api.github.com/users/matrix-org/orgs","repos_url":"https://api.github.com/users/matrix-org/repos","events_url":"https://api.github.com/users/matrix-org/events{/privacy}","received_events_url":"https://api.github.com/users/matrix-org/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/matrix-org/sytest","description":"Black-box integration testing for Matrix homeservers","fork":false,"url":"https://api.github.com/repos/matrix-org/sytest","forks_url":"https://api.github.com/repos/matrix-org/sytest/forks","keys_url":"https://api.github.com/repos/matrix-org/sytest/keys{/key_id}","collaborators_url":"https://api.github.com/repos/matrix-org/sytest/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/matrix-org/sytest/teams","hooks_url":"https://api.github.com/repos/matrix-org/sytest/hooks","issue_events_url":"https://api.github.com/repos/matrix-org/sytest/issues/events{/number}","events_url":"https://api.github.com/repos/matrix-org/sytest/events","assignees_url":"https://api.github.com/repos/matrix-org/sytest/assignees{/user}","branches_url":"https://api.github.com/repos/matrix-org/sytest/branches{/branch}","tags_url":"https://api.github.com/repos/matrix-org/sytest/tags","blobs_url":"https://api.github.com/repos/matrix-org/sytest/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/matrix-org/sytest/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/matrix-org/sytest/git/refs{/sha}","trees_url":"https://api.github.com/repos/matrix-org/sytest/git/trees{/sha}","statuses_url":"https://api.github.com/repos/matrix-org/sytest/statuses/{sha}","languages_url":"https://api.github.com/repos/matrix-org/sytest/languages","stargazers_url":"https://api.github.com/repos/matrix-org/sytest/stargazers","contributors_url":"https://api.github.com/repos/matrix-org/sytest/contributors","subscribers_url":"https://api.github.com/repos/matrix-org/sytest/subscribers","subscription_url":"https://api.github.com/repos/matrix-org/sytest/subscription","commits_url":"https://api.github.com/repos/matrix-org/sytest/commits{/sha}","git_commits_url":"https://api.github.com/repos/matrix-org/sytest/git/commits{/sha}","comments_url":"https://api.github.com/repos/matrix-org/sytest/comments{/number}","issue_comment_url":"https://api.github.com/repos/matrix-org/sytest/issues/comments{/number}","contents_url":"https://api.github.com/repos/matrix-org/sytest/contents/{+path}","compare_url":"https://api.github.com/repos/matrix-org/sytest/compare/{base}...{head}","merges_url":"https://api.github.com/repos/matrix-org/sytest/merges","archive_url":"https://api.github.com/repos/matrix-org/sytest/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/matrix-org/sytest/downloads","issues_url":"https://api.github.com/repos/matrix-org/sytest/issues{/number}","pulls_url":"https://api.github.com/repos/matrix-org/sytest/pulls{/number}","milestones_url":"https://api.github.com/repos/matrix-org/sytest/milestones{/number}","notifications_url":"https://api.github.com/repos/matrix-org/sytest/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/matrix-org/sytest/labels{/name}","releases_url":"https://api.github.com/repos/matrix-org/sytest/releases{/id}","created_at":"2014-08-28T17:05:43Z","updated_at":"2015-05-26T09:36:45Z","pushed_at":"2015-10-07T14:56:10Z","git_url":"git://github.com/matrix-org/sytest.git","ssh_url":"git@github.com:matrix-org/sytest.git","clone_url":"https://github.com/matrix-org/sytest.git","svn_url":"https://github.com/matrix-org/sytest","homepage":"","size":2066,"stargazers_count":2,"watchers_count":2,"language":"Perl","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":2,"default_branch":"master"}},"base":{"label":"matrix-org:develop","ref":"develop","sha":"42545167d3a1b5553531bb032431f83488d61f0b","user":{"login":"matrix-org","id":8418310,"avatar_url":"https://avatars.githubusercontent.com/u/8418310?v=3","gravatar_id":"","url":"https://api.github.com/users/matrix-org","html_url":"https://github.com/matrix-org","followers_url":"https://api.github.com/users/matrix-org/followers","following_url":"https://api.github.com/users/matrix-org/following{/other_user}","gists_url":"https://api.github.com/users/matrix-org/gists{/gist_id}","starred_url":"https://api.github.com/users/matrix-org/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/matrix-org/subscriptions","organizations_url":"https://api.github.com/users/matrix-org/orgs","repos_url":"https://api.github.com/users/matrix-org/repos","events_url":"https://api.github.com/users/matrix-org/events{/privacy}","received_events_url":"https://api.github.com/users/matrix-org/received_events","type":"Organization","site_admin":false},"repo":{"id":23436169,"name":"sytest","full_name":"matrix-org/sytest","owner":{"login":"matrix-org","id":8418310,"avatar_url":"https://avatars.githubusercontent.com/u/8418310?v=3","gravatar_id":"","url":"https://api.github.com/users/matrix-org","html_url":"https://github.com/matrix-org","followers_url":"https://api.github.com/users/matrix-org/followers","following_url":"https://api.github.com/users/matrix-org/following{/other_user}","gists_url":"https://api.github.com/users/matrix-org/gists{/gist_id}","starred_url":"https://api.github.com/users/matrix-org/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/matrix-org/subscriptions","organizations_url":"https://api.github.com/users/matrix-org/orgs","repos_url":"https://api.github.com/users/matrix-org/repos","events_url":"https://api.github.com/users/matrix-org/events{/privacy}","received_events_url":"https://api.github.com/users/matrix-org/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/matrix-org/sytest","description":"Black-box integration testing for Matrix homeservers","fork":false,"url":"https://api.github.com/repos/matrix-org/sytest","forks_url":"https://api.github.com/repos/matrix-org/sytest/forks","keys_url":"https://api.github.com/repos/matrix-org/sytest/keys{/key_id}","collaborators_url":"https://api.github.com/repos/matrix-org/sytest/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/matrix-org/sytest/teams","hooks_url":"https://api.github.com/repos/matrix-org/sytest/hooks","issue_events_url":"https://api.github.com/repos/matrix-org/sytest/issues/events{/number}","events_url":"https://api.github.com/repos/matrix-org/sytest/events","assignees_url":"https://api.github.com/repos/matrix-org/sytest/assignees{/user}","branches_url":"https://api.github.com/repos/matrix-org/sytest/branches{/branch}","tags_url":"https://api.github.com/repos/matrix-org/sytest/tags","blobs_url":"https://api.github.com/repos/matrix-org/sytest/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/matrix-org/sytest/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/matrix-org/sytest/git/refs{/sha}","trees_url":"https://api.github.com/repos/matrix-org/sytest/git/trees{/sha}","statuses_url":"https://api.github.com/repos/matrix-org/sytest/statuses/{sha}","languages_url":"https://api.github.com/repos/matrix-org/sytest/languages","stargazers_url":"https://api.github.com/repos/matrix-org/sytest/stargazers","contributors_url":"https://api.github.com/repos/matrix-org/sytest/contributors","subscribers_url":"https://api.github.com/repos/matrix-org/sytest/subscribers","subscription_url":"https://api.github.com/repos/matrix-org/sytest/subscription","commits_url":"https://api.github.com/repos/matrix-org/sytest/commits{/sha}","git_commits_url":"https://api.github.com/repos/matrix-org/sytest/git/commits{/sha}","comments_url":"https://api.github.com/repos/matrix-org/sytest/comments{/number}","issue_comment_url":"https://api.github.com/repos/matrix-org/sytest/issues/comments{/number}","contents_url":"https://api.github.com/repos/matrix-org/sytest/contents/{+path}","compare_url":"https://api.github.com/repos/matrix-org/sytest/compare/{base}...{head}","merges_url":"https://api.github.com/repos/matrix-org/sytest/merges","archive_url":"https://api.github.com/repos/matrix-org/sytest/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/matrix-org/sytest/downloads","issues_url":"https://api.github.com/repos/matrix-org/sytest/issues{/number}","pulls_url":"https://api.github.com/repos/matrix-org/sytest/pulls{/number}","milestones_url":"https://api.github.com/repos/matrix-org/sytest/milestones{/number}","notifications_url":"https://api.github.com/repos/matrix-org/sytest/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/matrix-org/sytest/labels{/name}","releases_url":"https://api.github.com/repos/matrix-org/sytest/releases{/id}","created_at":"2014-08-28T17:05:43Z","updated_at":"2015-05-26T09:36:45Z","pushed_at":"2015-10-07T14:56:10Z","git_url":"git://github.com/matrix-org/sytest.git","ssh_url":"git@github.com:matrix-org/sytest.git","clone_url":"https://github.com/matrix-org/sytest.git","svn_url":"https://github.com/matrix-org/sytest","homepage":"","size":2066,"stargazers_count":2,"watchers_count":2,"language":"Perl","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":2,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/matrix-org/sytest/pulls/18"},"html":{"href":"https://github.com/matrix-org/sytest/pull/18"},"issue":{"href":"https://api.github.com/repos/matrix-org/sytest/issues/18"},"comments":{"href":"https://api.github.com/repos/matrix-org/sytest/issues/18/comments"},"review_comments":{"href":"https://api.github.com/repos/matrix-org/sytest/pulls/18/comments"},"review_comment":{"href":"https://api.github.com/repos/matrix-org/sytest/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/matrix-org/sytest/pulls/18/commits"},"statuses":{"href":"https://api.github.com/repos/matrix-org/sytest/statuses/310b42b394729ede3f23ca02ee472e4ebe2da6c8"}}}}