def test_ensure_coverage_changes_status(self):
        """Verify that processing an item that has a preexisting 
        CoverageRecord can change the status of that CoverageRecord.
        """
        always = AlwaysSuccessfulCoverageProvider("Always successful",
                                                  self.input_identifier_types,
                                                  self.output_source)
        persistent = NeverSuccessfulCoverageProvider(
            "Persistent failures", self.input_identifier_types,
            self.output_source)
        transient = TransientFailureCoverageProvider(
            "Persistent failures", self.input_identifier_types,
            self.output_source)

        # Cover the same identifier multiple times, simulating all
        # possible states of a CoverageRecord. The same CoverageRecord
        # is used every time and the status is changed appropriately
        # after every run.
        c1 = persistent.ensure_coverage(self.identifier, force=True)
        eq_(CoverageRecord.PERSISTENT_FAILURE, c1.status)

        c2 = transient.ensure_coverage(self.identifier, force=True)
        eq_(c2, c1)
        eq_(CoverageRecord.TRANSIENT_FAILURE, c1.status)

        c3 = always.ensure_coverage(self.identifier, force=True)
        eq_(c3, c1)
        eq_(CoverageRecord.SUCCESS, c1.status)

        c4 = persistent.ensure_coverage(self.identifier, force=True)
        eq_(c4, c1)
        eq_(CoverageRecord.PERSISTENT_FAILURE, c1.status)
 def test_failure_for_ignored_item(self):
     provider = NeverSuccessfulCoverageProvider(
         self._db,
         self.input_identifier_types,
         self.output_source,
         operation="i will ignore you")
     result = provider.failure_for_ignored_item(self.identifier)
     assert isinstance(result, CoverageFailure)
     eq_(True, result.transient)
     eq_("Was ignored by CoverageProvider.", result.exception)
     eq_(self.identifier, result.obj)
     eq_(self.output_source, result.data_source)
    def test_irrelevant_provider_is_not_called(self):

        gutenberg_monitor = AlwaysSuccessfulCoverageProvider(
            "Gutenberg monitor", self.gutenberg, self.oclc)
        oclc_monitor = NeverSuccessfulCoverageProvider("OCLC monitor",
                                                       self.oclc,
                                                       self.overdrive)
        monitor = PresentationReadyMonitor(self._db,
                                           [gutenberg_monitor, oclc_monitor])
        result = monitor.prepare(self.work)

        # There were no failures.
        eq_([], result)

        # The monitor that takes Gutenberg identifiers as input ran.
        eq_([self.work.presentation_edition.primary_identifier],
            gutenberg_monitor.attempts)

        # The monitor that takes OCLC editions as input did not.
        # (If it had, it would have failed.)
        eq_([], oclc_monitor.attempts)

        # The work has not been set to presentation ready--that's
        # handled elsewhere.
        eq_(False, self.work.presentation_ready)
    def test_prepare_returns_failing_providers(self):

        success = AlwaysSuccessfulCoverageProvider("Monitor 1", self.gutenberg,
                                                   self.oclc)
        failure = NeverSuccessfulCoverageProvider("Monitor 2", self.gutenberg,
                                                  self.overdrive)
        monitor = PresentationReadyMonitor(self._db, [success, failure])
        result = monitor.prepare(self.work)
        eq_([failure], result)
    def test_ensure_coverage_persistent_coverage_failure(self):

        provider = NeverSuccessfulCoverageProvider("Never successful",
                                                   self.input_identifier_types,
                                                   self.output_source)
        failure = provider.ensure_coverage(self.edition)

        # A CoverageRecord has been created to memorialize the
        # persistent failure.
        assert isinstance(failure, CoverageRecord)
        eq_("What did you expect?", failure.exception)

        # Here it is in the database.
        [record] = self._db.query(CoverageRecord).all()
        eq_(record, failure)

        # The coverage provider's timestamp was not updated, because
        # we're using ensure_coverage.
        eq_([], self._db.query(Timestamp).all())
 def test_make_batch_presentation_ready_sets_exception_on_failure(self):
     success = AlwaysSuccessfulCoverageProvider("Provider 1",
                                                self.gutenberg, self.oclc)
     failure = NeverSuccessfulCoverageProvider("Provider 2", self.gutenberg,
                                               self.overdrive)
     monitor = PresentationReadyMonitor(self._db, [success, failure])
     monitor.process_batch([self.work])
     eq_(False, self.work.presentation_ready)
     eq_("Provider(s) failed: Provider 2",
         self.work.presentation_ready_exception)
    def test_run_on_specific_identifiers_respects_cutoff_time(self):

        last_run = datetime.datetime(2016, 1, 1)

        # Once upon a time we successfully added coverage for
        # self.identifier.
        record, ignore = CoverageRecord.add_for(self.identifier,
                                                self.output_source)
        record.timestamp = last_run

        # But now something has gone wrong, and if we ever run the
        # coverage provider again we will get a persistent failure.
        provider = NeverSuccessfulCoverageProvider("Persistent failure",
                                                   self.input_identifier_types,
                                                   self.output_source,
                                                   cutoff_time=last_run)

        # You might think this would result in a persistent failure...
        (success, transient_failure,
         persistent_failure), records = (provider.run_on_specific_identifiers(
             [self.identifier]))

        # ...but we get an automatic success. We didn't even try to
        # run the coverage provider on self.identifier because the
        # coverage record was up-to-date.
        eq_(1, success)
        eq_(0, persistent_failure)
        eq_([], records)

        # But if we move the cutoff time forward, the provider will run
        # on self.identifier and fail.
        provider.cutoff_time = datetime.datetime(2016, 2, 1)
        (success, transient_failure,
         persistent_failure), records = (provider.run_on_specific_identifiers(
             [self.identifier]))
        eq_(0, success)
        eq_(1, persistent_failure)

        # The formerly successful CoverageRecord will be updated to
        # reflect the failure.
        eq_(records[0], record)
        eq_("What did you expect?", record.exception)
    def test_never_successful(self):

        # We start with no CoverageRecords and no Timestamp.
        eq_([], self._db.query(CoverageRecord).all())
        eq_([], self._db.query(Timestamp).all())

        provider = NeverSuccessfulCoverageProvider("Never successful",
                                                   self.input_identifier_types,
                                                   self.output_source)
        provider.run()

        # We have a CoverageRecord that signifies failure.
        [record] = self._db.query(CoverageRecord).all()
        eq_(self.identifier, record.identifier)
        eq_(self.output_source, self.output_source)
        eq_("What did you expect?", record.exception)

        # But the coverage provider did run, and the timestamp is now set.
        [timestamp] = self._db.query(Timestamp).all()
        eq_("Never successful", timestamp.service)
    def test_operation_included_in_records(self):
        provider = AlwaysSuccessfulCoverageProvider(
            "Always successful",
            self.input_identifier_types,
            self.output_source,
            operation=CoverageRecord.SYNC_OPERATION)
        result = provider.ensure_coverage(self.edition)

        # The provider's operation is added to the record on success
        [record] = self._db.query(CoverageRecord).all()
        eq_(record.operation, CoverageRecord.SYNC_OPERATION)
        self._db.delete(record)

        provider = NeverSuccessfulCoverageProvider(
            "Never successful",
            self.input_identifier_types,
            self.output_source,
            operation=CoverageRecord.REAP_OPERATION)
        result = provider.ensure_coverage(self.edition)

        # The provider's operation is added to the record on failure
        [record] = self._db.query(CoverageRecord).all()
        eq_(record.operation, CoverageRecord.REAP_OPERATION)
    def test_process_batch_and_handle_results(self):

        e1, p1 = self._edition(with_license_pool=True)
        i1 = e1.primary_identifier

        e2, p2 = self._edition(with_license_pool=True)
        i2 = e2.primary_identifier

        success_provider = AlwaysSuccessfulCoverageProvider(
            "Success",
            self.input_identifier_types,
            self.output_source,
            operation="i succeed")

        batch = [i1, i2]
        counts, successes = success_provider.process_batch_and_handle_results(
            batch)

        # Two successes.
        eq_((2, 0, 0), counts)

        # Each represented with a CoverageRecord with status='success'
        assert all(isinstance(x, CoverageRecord) for x in successes)
        eq_([CoverageRecord.SUCCESS] * 2, [x.status for x in successes])

        # Each associated with one of the identifiers...
        eq_(set([i1, i2]), set([x.identifier for x in successes]))

        # ...and with the coverage provider's operation.
        eq_(['i succeed'] * 2, [x.operation for x in successes])

        # Now try a different CoverageProvider which creates transient
        # failures.
        transient_failure_provider = TransientFailureCoverageProvider(
            "Transient failure",
            self.input_identifier_types,
            self.output_source,
            operation="i fail transiently")
        counts, failures = transient_failure_provider.process_batch_and_handle_results(
            batch)
        # Two transient failures.
        eq_((0, 2, 0), counts)

        # New coverage records were added to track the transient
        # failures.
        eq_([CoverageRecord.TRANSIENT_FAILURE] * 2,
            [x.status for x in failures])
        eq_(["i fail transiently"] * 2, [x.operation for x in failures])

        # Another way of getting transient failures is to just ignore every
        # item you're told to process.
        task_ignoring_provider = TaskIgnoringCoverageProvider(
            "Ignores all tasks",
            self.input_identifier_types,
            self.output_source,
            operation="i ignore")
        counts, records = task_ignoring_provider.process_batch_and_handle_results(
            batch)

        eq_((0, 2, 0), counts)
        eq_([CoverageRecord.TRANSIENT_FAILURE] * 2,
            [x.status for x in records])
        eq_(["i ignore"] * 2, [x.operation for x in records])

        # Or you can go really bad and have persistent failures.
        persistent_failure_provider = NeverSuccessfulCoverageProvider(
            "Persistent failure",
            self.input_identifier_types,
            self.output_source,
            operation="i will always fail")
        counts, results = persistent_failure_provider.process_batch_and_handle_results(
            batch)

        # Two persistent failures.
        eq_((0, 0, 2), counts)
        assert all([isinstance(x, CoverageRecord) for x in results])
        eq_(["What did you expect?", "What did you expect?"],
            [x.exception for x in results])
        eq_([CoverageRecord.PERSISTENT_FAILURE] * 2,
            [x.status for x in results])
        eq_(["i will always fail"] * 2, [x.operation for x in results])