@RunWith(JMock.class)
public class FormatTransactionAsCsvRowTest {
  private final Mockery mockery = new Mockery();

  private final CsvFormat<LocalDate> dateFormat = mockery.mock(CsvFormat.class, "date format");
  private final CsvFormat<Category> categoryFormat =
      mockery.mock(CsvFormat.class, "category format");
  private final CsvFormat<Amount> amountFormat = mockery.mock(CsvFormat.class, "amount format");
  private final TransactionCsvFormat transactionCsvFormat =
      new TransactionCsvFormat(dateFormat, categoryFormat, amountFormat);

  private LocalDate anyNonNullDate = new LocalDate(2012, 11, 14);
  private Category anyNonNullCategory = new Category("Bowling Winnings");
  private Amount anyNonNullAmount = Amount.cents(250);

  @Test
  public void happyPath() throws Exception {
    mockery.checking(
        new Expectations() {
          {
            allowing(dateFormat).format(with(any(LocalDate.class)));
            will(returnValue("::the date::"));

            allowing(categoryFormat).format(with(any(Category.class)));
            will(returnValue("::the category::"));

            allowing(amountFormat).format(with(any(Amount.class)));
            will(returnValue("::the amount::"));
          }
        });

    final Transaction transaction =
        new Transaction(anyNonNullDate, anyNonNullCategory, anyNonNullAmount);
    final String rowText = transactionCsvFormat.format(transaction);
    assertThat(
        rowText,
        matches(
            Pattern.compile(
                "\\s*\"::the date::\","
                    + "\\s*\"::the category::\","
                    + "\\s*\"::the amount::\"\\s*")));
  }

  @Test
  public void nullTransaction() throws Exception {
    try {
      transactionCsvFormat.format(null);
      fail("How did you format a null transaction?!");
    } catch (ProgrammerMistake success) {
    }
  }
}
@RunWith(RobolectricTestRunner.class)
public class HandleExportAllTransactionsTest {
  private Mockery mockery = new Mockery();
  private final BrowseTransactionsModel browseTransactionsModel =
      mockery.mock(BrowseTransactionsModel.class);
  private final ExportAllTransactionsAction exportAllTransactionsAction =
      mockery.mock(ExportAllTransactionsAction.class);
  private final AndroidDevicePublicStorageGateway androidDevicePublicStorageGateway =
      mockery.mock(AndroidDevicePublicStorageGateway.class);

  private final BrowseTransactionsActivity browseTransactionsActivity =
      new BrowseTransactionsActivity(
          null,
          exportAllTransactionsAction,
          androidDevicePublicStorageGateway,
          browseTransactionsModel);

  @Before
  public void initializeActivity() {
    browseTransactionsActivity.onCreate(null);
    browseTransactionsActivity.setCollaborators(
        exportAllTransactionsAction, androidDevicePublicStorageGateway, browseTransactionsModel);
  }

  @Test
  public void happyPath() throws Exception {
    final Collection<Object> anyValidNonTrivialCollectionOfTransactions =
        Lists.newArrayList(new Object(), new Object(), new Object());

    mockery.checking(
        new Expectations() {
          {
            allowing(browseTransactionsModel).findAllTransactions();

            will(returnValue(anyValidNonTrivialCollectionOfTransactions));

            // SMELL Irrelevant detail
            ignoring(androidDevicePublicStorageGateway).findPublicExternalStorageDirectory();

            allowing(exportAllTransactionsAction).execute(with(any(List.class)));
            // succeeds by not throwing an exception
          }
        });

    pressExportAllTransactionsButton(browseTransactionsActivity);

    assertLastToastMatchesRegex("Exported all transactions to (.+)\\.csv");
  }

  @Test
  public void mediaNotAvailable() throws Exception {
    mockery.checking(
        new Expectations() {
          {
            allowing(browseTransactionsModel).findAllTransactions();

            allowing(androidDevicePublicStorageGateway).findPublicExternalStorageDirectory();
            will(throwException(new PublicStorageMediaNotAvailableException()));

            never(exportAllTransactionsAction);
          }
        });

    pressExportAllTransactionsButton(browseTransactionsActivity);

    assertLastToastMatchesRegex(
        "No place to which to export the transactions. Insert an SD card or connect an "
            + "external storage device and try again.");
  }

  @Test
  public void mediaNotWritable() throws Exception {
    mockery.checking(
        new Expectations() {
          {
            allowing(browseTransactionsModel).findAllTransactions();

            allowing(androidDevicePublicStorageGateway).findPublicExternalStorageDirectory();
            will(
                throwException(
                    new PublicStorageMediaNotWritableException(
                        new File("/mnt/sdcard/TrackEveryPenny.csv"))));

            never(exportAllTransactionsAction);
          }
        });

    pressExportAllTransactionsButton(browseTransactionsActivity);

    assertLastToastMatchesRegex(
        "Permission denied trying to export the transactions to file "
            + "/mnt/sdcard/TrackEveryPenny.csv");
  }

  @Test
  public void modelBlowsUpInAnUnavoidableWay() throws Exception {
    mockery.checking(
        new Expectations() {
          {
            allowing(browseTransactionsModel).findAllTransactions();
            will(throwException(new InternalStorageException()));
          }
        });

    pressExportAllTransactionsButton(browseTransactionsActivity);

    assertLastToastMatchesRegex(
        "Something strange just happened. Try again. You might need to reinstall the "
            + "application. I feel embarrassed and ashamed.");
  }

  @Test
  public void exportActionBlowsUpInAnUnavoidableWay() throws Exception {
    mockery.checking(
        new Expectations() {
          {
            // SMELL How can I ignore these irrelevant details?
            ignoring(browseTransactionsModel);
            ignoring(androidDevicePublicStorageGateway);

            allowing(exportAllTransactionsAction).execute(with(any(List.class)));
            will(throwException(new InternalStorageException()));
          }
        });

    pressExportAllTransactionsButton(browseTransactionsActivity);

    assertLastToastMatchesRegex(
        "Something strange just happened. Try again. You might need to reinstall the "
            + "application. I feel embarrassed and ashamed.");
  }

  @Test
  public void exportWhateverTheModelProvides() throws Exception {
    final List<Transaction> anyNonEmptyListOfTransactions =
        ObjectMother.anyNonEmptyListOfTransactions();

    mockery.checking(
        new Expectations() {
          {
            allowing(browseTransactionsModel).findAllTransactions();
            will(returnValue(anyNonEmptyListOfTransactions));

            ignoring(androidDevicePublicStorageGateway);

            oneOf(exportAllTransactionsAction).execute(with(anyNonEmptyListOfTransactions));
          }
        });

    pressExportAllTransactionsButton(browseTransactionsActivity);
  }

  private void pressExportAllTransactionsButton(
      BrowseTransactionsActivity browseTransactionsActivity) {
    browseTransactionsActivity.exportAllTransactions(
        browseTransactionsActivity.findViewById(R.id.exportAllTransactionsButton));
  }

  public static void assertLastToastMatchesRegex(String patternText) {
    ShadowHandler.idleMainLooper();
    assertThat(ShadowToast.getTextOfLatestToast(), matches(Pattern.compile(patternText)));
  }
}