// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>

#import "FirebaseCore/Extension/FirebaseCoreInternal.h"

#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif

#import "FBLPromise+Testing.h"

#include "Crashlytics/Crashlytics/Components/FIRCLSContext.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSCrashedMarkerFile.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSAnalyticsManager.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h"
#import "Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionArbiter.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSSettings.h"
#import "Crashlytics/UnitTests/Mocks/FIRCLSMockExistingReportManager.h"

#import "Crashlytics/Crashlytics/Settings/Models/FIRCLSApplicationIdentifierModel.h"
#import "Crashlytics/UnitTests/Mocks/FABMockApplicationIdentifierModel.h"
#import "Crashlytics/UnitTests/Mocks/FIRAppFake.h"
#import "Crashlytics/UnitTests/Mocks/FIRCLSMockReportManager.h"
#import "Crashlytics/UnitTests/Mocks/FIRCLSMockReportUploader.h"
#import "Crashlytics/UnitTests/Mocks/FIRCLSMockSettings.h"
#import "Crashlytics/UnitTests/Mocks/FIRCLSTempMockFileManager.h"
#import "Crashlytics/UnitTests/Mocks/FIRMockGDTCoreTransport.h"
#import "Crashlytics/UnitTests/Mocks/FIRMockInstallations.h"

#define TEST_API_KEY (@"DB5C8FA65C0D43419120FB96CFDBDE0C")
#define TEST_GOOGLE_APP_ID (@"1:632950151350:ios:d5b0d08d4f00f4b1")
#define TEST_INSTALL_ID (@"DC352568-33A7-4830-A9D8-20EA708F1905")
#define TEST_API_ENDPOINT (@"http://test.com")
#define TEST_BUNDLE_ID (@"com.crashlytics.test")
#define TEST_ANALYTICS_JSON \
  (@"{\"name\":\"some_name\",\"nested\":{\"object\":\"with_stuff\"},\"price\":100}")

@interface FIRCLSReportManagerTests : XCTestCase

@property(nonatomic, strong) FIRCLSMockReportManager *reportManager;
@property(nonatomic, strong) FIRCLSMockExistingReportManager *existingReportManager;
@property(nonatomic, strong) FIRCLSMockSettings *mockSettings;
@property(nonatomic, strong) FIRCLSMockReportUploader *mockReportUploader;
@property(nonatomic, strong) FIRCLSTempMockFileManager *fileManager;

@property(nonatomic, strong) FIRCLSDataCollectionArbiter *dataArbiter;
@property(nonatomic, strong) FIRCLSApplicationIdentifierModel *appIDModel;

@end

@implementation FIRCLSReportManagerTests

- (void)setUp {
  [super setUp];

  FIRSetLoggerLevel(FIRLoggerLevelMax);

  FIRCLSContextBaseInit();

  id fakeApp = [[FIRAppFake alloc] init];
  self.dataArbiter = [[FIRCLSDataCollectionArbiter alloc] initWithApp:fakeApp withAppInfo:@{}];

  self.fileManager = [[FIRCLSTempMockFileManager alloc] init];

  // Cleanup potential artifacts from other test files.
  if ([[NSFileManager defaultManager] fileExistsAtPath:[self.fileManager rootPath]]) {
    assert([self.fileManager removeItemAtPath:[self.fileManager rootPath]]);
  }

  // Delete cached settings
  [self.fileManager removeItemAtPath:_fileManager.settingsFilePath];

  FIRMockInstallations *iid = [[FIRMockInstallations alloc] initWithFID:@"test_token"];

  FIRMockGDTCORTransport *mockGoogleTransport =
      [[FIRMockGDTCORTransport alloc] initWithMappingID:@"id" transformers:nil target:0];
  self.appIDModel = [[FIRCLSApplicationIdentifierModel alloc] init];
  self.mockSettings = [[FIRCLSMockSettings alloc] initWithFileManager:self.fileManager
                                                           appIDModel:self.appIDModel];

  // Allow nil values only in tests
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
  FIRCLSManagerData *managerData =
      [[FIRCLSManagerData alloc] initWithGoogleAppID:TEST_GOOGLE_APP_ID
                                     googleTransport:mockGoogleTransport
                                       installations:iid
                                           analytics:nil
                                         fileManager:self.fileManager
                                         dataArbiter:self.dataArbiter
                                            settings:self.mockSettings
                                       onDemandModel:nil];
#pragma clang diagnostic pop

  self.mockReportUploader = [[FIRCLSMockReportUploader alloc] initWithManagerData:managerData];

  self.existingReportManager =
      [[FIRCLSMockExistingReportManager alloc] initWithManagerData:managerData
                                                    reportUploader:self.mockReportUploader];
  FIRCLSAnalyticsManager *analyticsManager = [[FIRCLSAnalyticsManager alloc] initWithAnalytics:nil];

  self.reportManager =
      [[FIRCLSMockReportManager alloc] initWithManagerData:managerData
                                     existingReportManager:self.existingReportManager
                                          analyticsManager:analyticsManager];
}

- (void)tearDown {
  self.reportManager = nil;

  if ([[NSFileManager defaultManager] fileExistsAtPath:[self.fileManager rootPath]]) {
    assert([self.fileManager removeItemAtPath:[self.fileManager rootPath]]);
  }

  FIRCLSContextBaseDeinit();

  [super tearDown];
}

#pragma mark - Path Helpers
- (NSString *)resourcePath {
#if SWIFT_PACKAGE
  NSBundle *bundle = SWIFTPM_MODULE_BUNDLE;
  return [bundle.resourcePath stringByAppendingPathComponent:@"Data"];
#else
  NSBundle *bundle = [NSBundle bundleForClass:[self class]];
  return bundle.resourcePath;
#endif
}

- (NSArray *)contentsOfActivePath {
  return [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.fileManager.activePath
                                                             error:nil];
}

- (NSArray *)contentsOfPreparedPath {
  return [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.fileManager.preparedPath
                                                             error:nil];
}

- (NSArray *)contentsOfProcessingPath {
  return [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.fileManager.processingPath
                                                             error:nil];
}

#pragma mark - Report Helpers
- (FIRCLSInternalReport *)createActiveReport {
  NSString *reportPath =
      [self.fileManager.activePath stringByAppendingPathComponent:@"my_session_id"];
  FIRCLSInternalReport *report = [[FIRCLSInternalReport alloc] initWithPath:reportPath
                                                        executionIdentifier:@"my_session_id"];

  if (![self.fileManager createDirectoryAtPath:report.path]) {
    return nil;
  }

  if (![self createMetadata:
                 @"{\"identity\":{\"api_key\":\"my_key\",\"session_id\":\"my_session_id\"}}\n"
                  forReport:report]) {
    return nil;
  }
  return report;
}

- (BOOL)createFileWithContents:(NSString *)contents atPath:(NSString *)path {
  return [self.fileManager.underlyingFileManager
      createFileAtPath:path
              contents:[contents dataUsingEncoding:NSUTF8StringEncoding]
            attributes:nil];
}

- (BOOL)createMetadata:(NSString *)value forReport:(FIRCLSInternalReport *)report {
  return [self createFileWithContents:value atPath:[report metadataPath]];
}

#pragma mark - Property Helpers
- (NSArray *)prepareAndSubmitReportArray {
  return self.mockReportUploader.prepareAndSubmitReportArray;
}

- (NSArray *)uploadReportArray {
  return self.mockReportUploader.uploadReportArray;
}

#pragma mark - File/Directory Handling
- (void)testCreatesNewReportOnStart {
  FBLPromise<NSNumber *> *promise = [self->_reportManager startWithProfiling];
  FBLWaitForPromisesWithTimeout(1.0);

  XCTAssertTrue([promise.value boolValue]);
  XCTAssertEqual([[self contentsOfActivePath] count], 1);
}

- (void)waitForPromise:(FBLPromise<NSNumber *> *)promise {
  [self waitForPromise:promise withTimeout:1.0];
}

- (void)waitForPromise:(FBLPromise<NSNumber *> *)promise withTimeout:(double)timeout {
  __block NSNumber *value = nil;
  __block NSError *error = nil;

  XCTestExpectation *expectation =
      [[XCTestExpectation alloc] initWithDescription:@"waiting on promise"];
  [[promise then:^id _Nullable(NSNumber *_Nullable innerValue) {
    value = innerValue;
    [expectation fulfill];
    return nil;
  }] catch:^(NSError *_Nonnull innerError) {
    error = innerError;
    [expectation fulfill];
  }];

  [self waitForExpectations:@[ expectation ] timeout:timeout];
  XCTAssertNil(error);
  XCTAssertTrue([value boolValue]);
}

- (void)startReportManager {
  [self waitForPromise:[self startReportManagerWithDataCollectionEnabled:YES]];
}

- (FBLPromise<NSNumber *> *)startReportManagerWithDataCollectionEnabled:(BOOL)enabled {
  [self.dataArbiter setCrashlyticsCollectionEnabled:enabled];
  return [self.reportManager startWithProfiling];
}

- (void)processReports:(BOOL)send andExpectReports:(BOOL)reportsExpected {
  XCTestExpectation *processReportsComplete =
      [[XCTestExpectation alloc] initWithDescription:@"processReports: complete"];
  __block BOOL reportsAvailable = NO;
  [[[self.reportManager checkForUnsentReports]
      then:^id _Nullable(FIRCrashlyticsReport *_Nullable report) {
        reportsAvailable = report ? true : false;
        if (send) {
          return [self->_reportManager sendUnsentReports];
        } else {
          return [self->_reportManager deleteUnsentReports];
        }
      }] then:^id _Nullable(id _Nullable ignored) {
    [processReportsComplete fulfill];
    return nil;
  }];
  [self waitForExpectations:@[ processReportsComplete ] timeout:1.0];
  if (reportsExpected) {
    XCTAssertTrue(reportsAvailable, "should have unsent reports");
  } else {
    XCTAssertFalse(reportsAvailable, "should not have unsent reports");
  }
}

- (void)processReports:(BOOL)send {
  [self processReports:send andExpectReports:YES];
}

- (void)testExistingUnimportantReportOnStart {
  // Create a report representing the last run and put it in place
  [self createActiveReport];

  // Report from the last run should get deleted, and a new
  // one should be created for this run.
  [self startReportManager];

  // If this is > 1 it means we're not cleaning up reports from previous runs.
  // If this == 0, it means we're not creating new reports.
  XCTAssertEqual([[self contentsOfActivePath] count], 1);

  XCTAssertEqual([self.prepareAndSubmitReportArray count], 0);
  XCTAssertEqual([self.uploadReportArray count], 0);
}

- (void)testMetricKitResolvesPromiseIfNoDiagnostics {
  // Create a report representing the last run and put it in place, then create a crashed file
  // marker and MetricKit diagnostic file so that MetricKit manager doesn't resolve the promise
  // immediately.
  [self createActiveReport];
  [self.existingReportManager setShouldHaveExistingReport];
  NSString *metricKitPath =
      [self.fileManager.cachesPath stringByAppendingString:@"/MetricKit/Diagnostics/"];
  [self.fileManager createFileAtPath:[[self.fileManager rootPath]
                                         stringByAppendingPathComponent:@"previously-crashed"]
                            contents:nil
                          attributes:nil];
  [self.fileManager createDirectoryAtPath:metricKitPath];
  [self.fileManager createFileAtPath:[metricKitPath stringByAppendingString:@"Diagnostics.txt"]
                            contents:nil
                          attributes:nil];

  // MetricKit manager should resolve its promise after 3 seconds.
  [self waitForPromise:[self startReportManagerWithDataCollectionEnabled:YES] withTimeout:4];
}

- (void)testExistingUnimportantReportOnStartWithDataCollectionDisabled {
  // create a report and put it in place
  XCTAssertNotNil([self createActiveReport]);

  // Starting with data collection disabled should report in nothing changing
  [self startReportManagerWithDataCollectionEnabled:NO];
  FBLWaitForPromisesWithTimeout(1.0);

  XCTAssertEqual([[self contentsOfActivePath] count], 1);

  XCTAssertEqual([self.prepareAndSubmitReportArray count], 0);
  XCTAssertEqual([self.uploadReportArray count], 0);
}

- (void)testExistingReportOnStart {
  // create a report and put it in place
  FIRCLSInternalReport *report = [self createActiveReport];

  // create a signal file so it is considering worth reporting
  XCTAssertTrue([self createFileWithContents:@"signal"
                                      atPath:[report pathForContentFile:FIRCLSReportSignalFile]]);

  XCTAssertEqual([[self contentsOfActivePath] count], 1);

  [self startReportManager];

  // verify that processReports won't get called.
  [self processReports:YES andExpectReports:NO];

  XCTAssertEqual([[self contentsOfActivePath] count], 1, @"should contain only the current report");

  // should call report manager once for that report
  XCTAssertEqual([self.prepareAndSubmitReportArray count], 1);
  XCTAssertEqualObjects(self.prepareAndSubmitReportArray[0][@"process"], @(YES));
  XCTAssertEqualObjects(self.prepareAndSubmitReportArray[0][@"urgent"], @(NO));
}

- (void)testExistingReportOnStartWithDataCollectionDisabledThenEnabled {
  // create a report and put it in place
  FIRCLSInternalReport *report = [self createActiveReport];

  // create a signal file so it is considering worth reporting
  XCTAssertTrue([self createFileWithContents:@"signal"
                                      atPath:[report pathForContentFile:FIRCLSReportSignalFile]]);

  XCTAssertEqual([[self contentsOfActivePath] count], 1);

  FBLPromise<NSNumber *> *promise = [self startReportManagerWithDataCollectionEnabled:NO];

  XCTAssertEqual([[self contentsOfActivePath] count], 2,
                 @"should contain the current and old reports");

  // should call report manager once for that report
  XCTAssertEqual([self.prepareAndSubmitReportArray count], 0);

  // We can turn data collection on instead of calling processReports.
  [self.dataArbiter setCrashlyticsCollectionEnabled:YES];
  [self waitForPromise:promise];

  XCTAssertEqual([[self contentsOfActivePath] count], 1, @"should contain only the current report");

  // should call report manager once for that report
  XCTAssertEqual([self.prepareAndSubmitReportArray count], 1);
  XCTAssertEqualObjects(self.prepareAndSubmitReportArray[0][@"process"], @(YES));
  XCTAssertEqualObjects(self.prepareAndSubmitReportArray[0][@"urgent"], @(NO));
}

- (void)testExistingReportOnStartWithDataCollectionDisabledAndSend {
  // create a report and put it in place
  FIRCLSInternalReport *report = [self createActiveReport];

  // create a signal file so it is considering worth reporting
  XCTAssertTrue([self createFileWithContents:@"signal"
                                      atPath:[report pathForContentFile:FIRCLSReportSignalFile]]);

  XCTAssertEqual([[self contentsOfActivePath] count], 1);

  [self startReportManagerWithDataCollectionEnabled:NO];

  XCTAssertEqual([[self contentsOfActivePath] count], 2,
                 @"should contain the current and old reports");

  // should call report manager once for that report
  XCTAssertEqual([self.prepareAndSubmitReportArray count], 0);

  [self processReports:YES];

  XCTAssertEqual([[self contentsOfActivePath] count], 1, @"should contain only the current report");

  // should call report manager once for that report
  XCTAssertEqual([self.prepareAndSubmitReportArray count], 1);
  XCTAssertEqualObjects(self.prepareAndSubmitReportArray[0][@"process"], @(YES));
  XCTAssertEqualObjects(self.prepareAndSubmitReportArray[0][@"urgent"], @(NO));

  // Calling processReports again should not call the callback.
  // Technically, the behavior is unspecified.
  [self processReports:YES andExpectReports:NO];
}

- (void)testExistingReportOnStartWithDataCollectionDisabledAndDelete {
  // create a report and put it in place
  FIRCLSInternalReport *report = [self createActiveReport];

  // create a signal file so it is considering worth reporting
  XCTAssertTrue([self createFileWithContents:@"signal"
                                      atPath:[report pathForContentFile:FIRCLSReportSignalFile]]);

  XCTAssertEqual([[self contentsOfActivePath] count], 1);

  [self startReportManagerWithDataCollectionEnabled:NO];

  XCTAssertEqual([[self contentsOfActivePath] count], 2,
                 @"should contain the current and old reports");

  // should call report manager once for that report
  XCTAssertEqual([self.prepareAndSubmitReportArray count], 0);

  [self processReports:NO];

  XCTAssertEqual([[self contentsOfActivePath] count], 1, @"should contain only the current report");

  // Should not call report manager for that report.
  XCTAssertEqual([self.prepareAndSubmitReportArray count], 0);
}

- (void)testExistingUrgentReportOnStart {
  // create a report and put it in place
  FIRCLSInternalReport *report = [self createActiveReport];

  // create a signal file so it is considering worth reporting
  XCTAssertTrue([self createFileWithContents:@"signal"
                                      atPath:[report pathForContentFile:FIRCLSReportSignalFile]]);

  XCTAssertEqual([[self contentsOfActivePath] count], 1);

  // Put the launch marker in place
  [self.reportManager.launchMarker createLaunchFailureMarker];

  // should call back to the delegate on start
  [self startReportManager];
  XCTAssertEqual([[self contentsOfActivePath] count], 1, @"should contain only the current report");

  // should call report manager once for that report
  XCTAssertEqual([self.prepareAndSubmitReportArray count], 1);
  XCTAssertEqualObjects(self.prepareAndSubmitReportArray[0][@"process"], @(YES));
  XCTAssertEqualObjects(self.prepareAndSubmitReportArray[0][@"urgent"], @(YES));
}

- (void)testExistingUrgentReportOnStartWithDataCollectionDisabled {
  // create a report and put it in place
  FIRCLSInternalReport *report = [self createActiveReport];

  // create a signal file so it is considering worth reporting
  XCTAssertTrue([self createFileWithContents:@"signal"
                                      atPath:[report pathForContentFile:FIRCLSReportSignalFile]]);

  XCTAssertEqual([[self contentsOfActivePath] count], 1);

  // Put the launch marker in place
  [self.reportManager.launchMarker createLaunchFailureMarker];

  // Should wait for processReports: to be called.
  [self startReportManagerWithDataCollectionEnabled:NO];

  XCTAssertEqual([[self contentsOfActivePath] count], 2, @"the report hasn't been sent");

  XCTAssertEqual([self.prepareAndSubmitReportArray count], 0);

  [self processReports:YES];

  XCTAssertEqual([[self contentsOfActivePath] count], 1, @"should contain only current report");

  XCTAssertEqual([self.prepareAndSubmitReportArray count], 1);
  XCTAssertEqualObjects(self.prepareAndSubmitReportArray[0][@"process"], @(YES));

  // If data collection is disabled, you can never send the report urgently / blocking
  // startup because you need to call a method after startup to send the report
  XCTAssertEqualObjects(self.prepareAndSubmitReportArray[0][@"urgent"], @(NO));
}

- (void)testFilesLeftInProcessing {
  // put report in processing
  FIRCLSInternalReport *report = [self createActiveReport];
  XCTAssert([_fileManager createDirectoryAtPath:_fileManager.processingPath]);
  XCTAssert([_fileManager moveItemAtPath:[report path] toDirectory:_fileManager.processingPath]);

  [self startReportManager];

  // we should not process reports left over in processing
  XCTAssertEqual([[self contentsOfProcessingPath] count], 0, @"Processing should be cleared");

  XCTAssertEqual([self.prepareAndSubmitReportArray count], 1);
  XCTAssertEqualObjects(self.prepareAndSubmitReportArray[0][@"process"], @(NO));
  XCTAssertEqualObjects(self.prepareAndSubmitReportArray[0][@"urgent"], @(NO));
}

/*
 * This tests an edge case where there is a report in processing. For the purposes of unsent
 * reports these are not shown to the developer, but they are uploaded / deleted upon
 * calling send / delete.
 */
- (void)testFilesLeftInProcessingWithDataCollectionDisabled {
  // Put report in processing.
  FIRCLSInternalReport *report = [self createActiveReport];
  XCTAssert([_fileManager createDirectoryAtPath:_fileManager.processingPath]);
  XCTAssert([_fileManager moveItemAtPath:[report path] toDirectory:_fileManager.processingPath]);

  [self startReportManagerWithDataCollectionEnabled:NO];

  // Nothing should have happened yet.
  XCTAssertEqual([[self contentsOfProcessingPath] count], 1,
                 @"Processing should still have the report");
  XCTAssertEqual([self.prepareAndSubmitReportArray count], 0);

  // We don't expect reports here because we don't consider processing or prepared
  // reports as unsent as they need to be marked for sending before being placed
  // in those directories.
  [self processReports:YES andExpectReports:NO];

  // We should not process reports left over in processing.
  XCTAssertEqual([[self contentsOfProcessingPath] count], 0, @"Processing should be cleared");
  XCTAssertEqual([[self contentsOfPreparedPath] count], 0, @"Prepared should be cleared");

  XCTAssertEqual([self.prepareAndSubmitReportArray count], 1);
  XCTAssertEqualObjects(self.prepareAndSubmitReportArray[0][@"process"], @(NO));
  XCTAssertEqualObjects(self.prepareAndSubmitReportArray[0][@"urgent"], @(NO));
}

- (void)testFilesLeftInPrepared {
  // Drop a phony multipart-mime file in here, with non-zero contents.
  XCTAssert([_fileManager createDirectoryAtPath:_fileManager.preparedPath]);
  NSString *path = [_fileManager.preparedPath stringByAppendingPathComponent:@"phony-report"];
  path = [path stringByAppendingPathExtension:@"multipart-mime"];

  XCTAssertTrue([[_fileManager underlyingFileManager]
      createFileAtPath:path
              contents:[@"contents" dataUsingEncoding:NSUTF8StringEncoding]
            attributes:nil]);

  [self startReportManager];

  // Reports should be moved out of prepared
  XCTAssertEqual([[self contentsOfPreparedPath] count], 0, @"Prepared should be cleared");

  XCTAssertEqual([self.prepareAndSubmitReportArray count], 0);
  XCTAssertEqual([self.uploadReportArray count], 1);
  XCTAssertEqualObjects(self.uploadReportArray[0][@"path"], path);
}

/*
 * This tests an edge case where there is a report in prepared. For the purposes of unsent
 * reports these are not shown to the developer, but they are uploaded / deleted upon
 * calling send / delete.
 */
- (void)testFilesLeftInPreparedWithDataCollectionDisabled {
  // drop a phony multipart-mime file in here, with non-zero contents
  XCTAssert([_fileManager createDirectoryAtPath:_fileManager.preparedPath]);
  NSString *path = [_fileManager.preparedPath stringByAppendingPathComponent:@"phony-report"];
  path = [path stringByAppendingPathExtension:@"multipart-mime"];

  XCTAssertTrue([[_fileManager underlyingFileManager]
      createFileAtPath:path
              contents:[@"contents" dataUsingEncoding:NSUTF8StringEncoding]
            attributes:nil]);

  [self startReportManagerWithDataCollectionEnabled:NO];

  // Nothing should have happened yet.
  XCTAssertEqual([[self contentsOfPreparedPath] count], 1,
                 @"Prepared should still have the report");
  XCTAssertEqual([self.prepareAndSubmitReportArray count], 0);

  // We don't expect reports here because we don't consider processing or prepared
  // reports as unsent as they need to be marked for sending before being placed
  // in those directories.
  [self processReports:YES andExpectReports:NO];

  // Reports should be moved out of prepared
  XCTAssertEqual([[self contentsOfPreparedPath] count], 0, @"Prepared should be cleared");
  XCTAssertEqual([[self contentsOfProcessingPath] count], 0, @"Processing should be cleared");

  XCTAssertEqual([self.prepareAndSubmitReportArray count], 0);
  XCTAssertEqual([self.uploadReportArray count], 1);
  XCTAssertEqualObjects(self.uploadReportArray[0][@"path"], path);
}

- (void)testSuccessfulSubmission {
  // drop a phony multipart-mime file in here, with non-zero contents
  XCTAssert([_fileManager createDirectoryAtPath:_fileManager.preparedPath]);
  NSString *path = [_fileManager.preparedPath stringByAppendingPathComponent:@"phony-report"];
  path = [path stringByAppendingPathExtension:@"multipart-mime"];

  XCTAssertTrue([[_fileManager underlyingFileManager]
      createFileAtPath:path
              contents:[@"contents" dataUsingEncoding:NSUTF8StringEncoding]
            attributes:nil]);

  [self startReportManager];

  // we should not process reports left over in processing
  XCTAssertEqual([[self contentsOfProcessingPath] count], 0, @"Processing should be cleared");

  XCTAssertEqual([self.prepareAndSubmitReportArray count], 0);
  XCTAssertEqual([self.uploadReportArray count], 1);
  XCTAssertEqualObjects(self.uploadReportArray[0][@"path"], path);

  [self.reportManager.operationQueue waitUntilAllOperationsAreFinished];

  // not 100% sure what to verify here
  // lol
}

- (void)testLogInvalidJSONAnalyticsEvents {
  NSDictionary *eventAsDict = @{
    @"price" : @(NAN),
    @"count" : @(INFINITY),
  };

  NSString *json = FIRCLSFIRAEventDictionaryToJSON(eventAsDict);
  XCTAssertEqualObjects(json, nil);
}

@end
