/** * Copyright (C) 2013 Open WhisperSystems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.whispersystems.bithub.tests.controllers; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.core.util.MultivaluedMapImpl; import org.apache.commons.codec.binary.Base64; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.whispersystems.bithub.auth.GithubWebhookAuthenticator; import org.whispersystems.bithub.client.CoinbaseClient; import org.whispersystems.bithub.client.GithubClient; import org.whispersystems.bithub.client.TransferFailedException; import org.whispersystems.bithub.config.RepositoryConfiguration; import org.whispersystems.bithub.controllers.GithubController; import org.whispersystems.bithub.entities.Author; import org.whispersystems.bithub.mappers.UnauthorizedHookExceptionMapper; import javax.ws.rs.core.MediaType; import java.io.InputStream; import java.math.BigDecimal; import java.util.LinkedList; import java.util.List; import java.util.Scanner; import io.dropwizard.auth.basic.BasicAuthProvider; import io.dropwizard.testing.junit.ResourceTestRule; import static org.fest.assertions.api.Assertions.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.*; public class GithubControllerTest { private static final BigDecimal BALANCE = new BigDecimal(10.01); private static final BigDecimal EXCHANGE_RATE = new BigDecimal(1.0); private final CoinbaseClient coinbaseClient = mock(CoinbaseClient.class); private final GithubClient githubClient = mock(GithubClient.class); // HTTP Basic Authentication data private final String authUsername = "TestUser"; private final String authPassword = "TestPassword"; private final String authRealm = GithubWebhookAuthenticator.REALM; private final String authString = "Basic " + Base64.encodeBase64String((authUsername + ":" + authPassword).getBytes()); private final String invalidUserAuthString = "Basic " + Base64.encodeBase64(("wrong:" + authPassword).getBytes()); private final String invalidPasswordAuthString = "Basic " + Base64.encodeBase64((authUsername + ":wrong").getBytes()); private final List<RepositoryConfiguration> repositories = new LinkedList<RepositoryConfiguration>() {{ add(new RepositoryConfiguration("https://github.com/moxie0/test")); add(new RepositoryConfiguration("https://github.com/moxie0/optin", "FREEBIE")); }}; @Rule public final ResourceTestRule resources = ResourceTestRule.builder() .addProvider(new UnauthorizedHookExceptionMapper()) .addProvider(new BasicAuthProvider<>(new GithubWebhookAuthenticator(authUsername, authPassword), authRealm)) .addResource(new GithubController(repositories, githubClient, coinbaseClient, new BigDecimal(0.02))) .build(); @Before public void setup() throws Exception, TransferFailedException { when(coinbaseClient.getAccountBalance()).thenReturn(BALANCE); when(coinbaseClient.getExchangeRate()).thenReturn(EXCHANGE_RATE); } protected String payload(String path) { InputStream is = this.getClass().getResourceAsStream(path); Scanner s = new Scanner(is).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; } @Test public void testInvalidRepository() throws Exception { String payloadValue = payload("/payloads/invalid_repo.json"); MultivaluedMapImpl post = new MultivaluedMapImpl(); post.add("payload", payloadValue); ClientResponse response = resources.client().resource("/v1/github/commits/") .header("X-Forwarded-For", "192.30.252.1") .header("Authorization", authString) .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE) .post(ClientResponse.class, post); assertThat(response.getStatus()).isEqualTo(401); } @Test public void testInvalidOrigin() throws Exception { String payloadValue = payload("/payloads/invalid_origin.json"); MultivaluedMapImpl post = new MultivaluedMapImpl(); post.add("payload", payloadValue); ClientResponse response = resources.client().resource("/v1/github/commits/") .header("X-Forwarded-For", "192.30.242.1") .header("Authorization", authString) .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE) .post(ClientResponse.class, post); assertThat(response.getStatus()).isEqualTo(401); } @Test public void testMissingAuth() throws Exception, TransferFailedException { String payloadValue = payload("/payloads/valid_commit.json"); MultivaluedMapImpl post = new MultivaluedMapImpl(); post.add("payload", payloadValue); ClientResponse response = resources.client().resource("/v1/github/commits/") .header("X-Forwarded-For", "192.30.252.1") .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE) .post(ClientResponse.class, post); assertThat(response.getStatus()).isEqualTo(401); } @Test public void testInvalidAuthUser() throws Exception, TransferFailedException { String payloadValue = payload("/payloads/valid_commit.json"); MultivaluedMapImpl post = new MultivaluedMapImpl(); post.add("payload", payloadValue); ClientResponse response = resources.client().resource("/v1/github/commits/") .header("X-Forwarded-For", "192.30.252.1") .header("Authorization", invalidUserAuthString) .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE) .post(ClientResponse.class, post); assertThat(response.getStatus()).isEqualTo(401); } @Test public void testInvalidAuthPassword() throws Exception, TransferFailedException { String payloadValue = payload("/payloads/valid_commit.json"); MultivaluedMapImpl post = new MultivaluedMapImpl(); post.add("payload", payloadValue); ClientResponse response = resources.client().resource("/v1/github/commits/") .header("X-Forwarded-For", "192.30.252.1") .header("Authorization", invalidPasswordAuthString) .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE) .post(ClientResponse.class, post); assertThat(response.getStatus()).isEqualTo(401); } @Test public void testOptOutCommit() throws Exception, TransferFailedException { String payloadValue = payload("/payloads/opt_out_commit.json"); MultivaluedMapImpl post = new MultivaluedMapImpl(); post.add("payload", payloadValue); ClientResponse response = resources.client().resource("/v1/github/commits/") .header("X-Forwarded-For", "192.30.252.1") .header("Authorization", authString) .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE) .post(ClientResponse.class, post); verify(coinbaseClient, never()).sendPayment(any(Author.class), any(BigDecimal.class), anyString()); } @Test public void testValidCommit() throws Exception, TransferFailedException { String payloadValue = payload("/payloads/valid_commit.json"); MultivaluedMapImpl post = new MultivaluedMapImpl(); post.add("payload", payloadValue); ClientResponse response = resources.client().resource("/v1/github/commits/") .header("X-Forwarded-For", "192.30.252.1") .header("Authorization", authString) .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE) .post(ClientResponse.class, post); verify(coinbaseClient).sendPayment(any(Author.class), eq(BALANCE.multiply(new BigDecimal(0.02))), anyString()); } @Test public void testNonMaster() throws Exception, TransferFailedException { String payloadValue = payload("/payloads/non_master_push.json"); MultivaluedMapImpl post = new MultivaluedMapImpl(); post.add("payload", payloadValue); ClientResponse response = resources.client().resource("/v1/github/commits/") .header("X-Forwarded-For", "192.30.252.1") .header("Authorization", authString) .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE) .post(ClientResponse.class, post); verify(coinbaseClient, never()).sendPayment(any(Author.class), eq(BALANCE.multiply(new BigDecimal(0.02))), anyString()); } @Test public void testValidMultipleCommitsMultipleAuthors() throws Exception, TransferFailedException { String payloadValue = payload("/payloads/multiple_commits_authors.json"); MultivaluedMapImpl post = new MultivaluedMapImpl(); post.add("payload", payloadValue); ClientResponse response = resources.client().resource("/v1/github/commits/") .header("X-Forwarded-For", "192.30.252.1") .header("Authorization", authString) .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE) .post(ClientResponse.class, post); verify(coinbaseClient, times(1)).sendPayment(any(Author.class), eq(BALANCE.multiply(new BigDecimal(0.02))), anyString()); verify(coinbaseClient, times(1)).sendPayment(any(Author.class), eq(BALANCE.subtract(BALANCE.multiply(new BigDecimal(0.02))) .multiply(new BigDecimal(0.02))), anyString()); } @Test public void testOptInCommit() throws Exception, TransferFailedException { String payloadValue = payload("/payloads/opt_in_commit.json"); MultivaluedMapImpl post = new MultivaluedMapImpl(); post.add("payload", payloadValue); ClientResponse response = resources.client().resource("/v1/github/commits/") .header("X-Forwarded-For", "192.30.252.1") .header("Authorization", authString) .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE) .post(ClientResponse.class, post); verify(coinbaseClient).sendPayment(any(Author.class), eq(BALANCE.multiply(new BigDecimal(0.02))), anyString()); } @Test public void testNoOptInCommit() throws Exception, TransferFailedException { String payloadValue = payload("/payloads/no_opt_in_commit.json"); MultivaluedMapImpl post = new MultivaluedMapImpl(); post.add("payload", payloadValue); ClientResponse response = resources.client().resource("/v1/github/commits/") .header("X-Forwarded-For", "192.30.252.1") .header("Authorization", authString) .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE) .post(ClientResponse.class, post); verify(coinbaseClient, never()).sendPayment(any(Author.class), any(BigDecimal.class), anyString()); } }