def test_delegates_to_agent_for_location(self, agent): """ When a request is made using the agent, the added agents are delegated to based on the URI location/authority. """ agent.add_agent(b'foo:8080', DummyAgent()) agent.add_agent(b'bar:8080', FailingAgent(RuntimeError('bar'))) agent.add_agent(b'foo:9090', FailingAgent(RuntimeError('9090'))) d = agent.request(b'GET', b'http://foo:8080') assert_that(d, succeeded(MatchesListwise([ MatchesListwise([Equals(b'GET'), Equals(b'http://foo:8080')]), MatchesDict({'headers': Is(None), 'bodyProducer': Is(None)}) ]))) # Scheme doesn't matter d = agent.request(b'GET', b'https://foo:8080') assert_that(d, succeeded(MatchesListwise([ MatchesListwise([Equals(b'GET'), Equals(b'https://foo:8080')]), MatchesDict({'headers': Is(None), 'bodyProducer': Is(None)}) ]))) # Path doesn't matter d = agent.request(b'GET', b'http://foo:8080/bar/baz') assert_that(d, succeeded(MatchesListwise([ MatchesListwise([ Equals(b'GET'), Equals(b'http://foo:8080/bar/baz')]), MatchesDict({'headers': Is(None), 'bodyProducer': Is(None)}) ]))) # Hostname *does* matter d = agent.request(b'GET', b'http://bar:8080') assert_that(d, failed(MatchesStructure(value=MatchesAll( IsInstance(RuntimeError), MatchesPredicate(str, Equals('bar')) )))) # Port *does* matter d = agent.request(b'GET', b'http://foo:9090') assert_that(d, failed(MatchesStructure(value=MatchesAll( IsInstance(RuntimeError), MatchesPredicate(str, Equals('9090')) )))) # Other args passed through d = agent.request(b'GET', b'http://foo:8080', 'bar', 'baz') assert_that(d, succeeded(MatchesListwise([ MatchesListwise([Equals(b'GET'), Equals(b'http://foo:8080')]), MatchesDict( {'headers': Equals('bar'), 'bodyProducer': Equals('baz')}) ])))
def test_no_folder_arg(self): """ An error is printed if we don't specify --folder """ stdout = StringIO() stderr = StringIO() self.assertThat( dispatch_magic_folder_api_command( ["add-snapshot", "--file", "foo"], stdout=stdout, stderr=stderr, client=None, ), failed( AfterPreprocessing( lambda f: isinstance(f.value, SystemExit) and f.value.code, Equals(1) ) ) ) self.assertThat( stdout.getvalue().strip(), Equals("Error: --folder / -n is required") )
def test_disconnected(self): """ If the transport is disconnected before the response is available, no ``RuntimeError`` is logged for finishing a disconnected request. """ result = Deferred() resource = StaticResource(result) d = render(resource, {}) resource._request.connectionLost(Failure(ConnectionDone())) result.callback(b"Some result") self.assertThat( d, failed( AfterPreprocessing( lambda reason: reason.type, Equals(ConnectionDone), ), ), ) # Since we're not a trial TestCase we don't have flushLoggedErrors. # The next best thing is to make sure any dangling Deferreds have been # garbage collected and then let the generic trial logic for failing # tests with logged errors kick in. gc.collect()
def test_get_events_non_200(self): """ When a request is made to Marathon's event stream, and a non-200 response code is returned, an error should be raised. """ data = [] d = self.cleanup_d(self.client.get_events({'test': data.append})) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/v2/events'), query={'event_type': ['test']})) self.assertThat(request.requestHeaders, HasHeader('accept', ['text/event-stream'])) request.setResponseCode(202) request.setHeader('Content-Type', 'text/event-stream') json_data = {'hello': 'world'} write_json_event(request, 'test', json_data) yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, 'Non-200 response code (202) for url: ' 'http://localhost:8080/v2/events?event_type=test'))) self.assertThat(data, Equals([])) request.finish() yield d
def test_get_events_incorrect_content_type(self): """ When a request is made to Marathon's event stream, and the content-type header value returned is not "text/event-stream", an error should be raised. """ data = [] d = self.cleanup_d(self.client.get_events({'test': data.append})) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/v2/events'), query={'event_type': ['test']})) self.assertThat(request.requestHeaders, HasHeader('accept', ['text/event-stream'])) request.setResponseCode(200) request.setHeader('Content-Type', 'application/json') json_data = {'hello': 'world'} write_json_event(request, 'test', json_data) yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, 'Expected header "Content-Type" to be "text/event-stream" but ' 'found "application/json" instead'))) self.assertThat(data, Equals([])) request.finish() yield d
def test_get_json_field_missing_content_type(self): """ When get_json_field is used to make a request and the content-type header is not set in the response headers then an error should be raised. """ d = self.cleanup_d( self.client.get_json_field('field-key', path='/my-path')) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/my-path'))) request.setResponseCode(200) # Twisted will set the content type to "text/html" by default but this # can be disabled by setting the default content type to None: # https://twistedmatrix.com/documents/current/api/twisted.web.server.Request.html#defaultContentType request.defaultContentType = None request.write(json.dumps({}).encode('utf-8')) request.finish() yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, 'Expected header "Content-Type" to be ' '"application/json" but header not found in response')))
def test_should_not_redeem(self, get_config, now, voucher, public_key): """ ``PaymentController.redeem`` raises ``ValueError`` if passed a voucher in a state when redemption should not be started. """ store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store controller = PaymentController( store, DummyRedeemer(public_key), default_token_count=100, clock=Clock(), ) self.assertThat( controller.redeem(voucher), succeeded(Always()), ) # Sanity check. It should be redeemed now. voucher_obj = controller.get_voucher(voucher) self.assertThat( voucher_obj.state.should_start_redemption(), Equals(False), ) self.assertThat( controller.redeem(voucher), failed(AfterPreprocessing( lambda f: f.type, Equals(ValueError), ), ), )
def test_non_json_response(self, voucher, counter, num_tokens): """ If the issuer responds with something that isn't JSON then the response is logged and the ``Deferred`` fires with a ``Failure`` wrapping ``UnexpectedResponse``. """ issuer = UnexpectedResponseRedemption() treq = treq_for_loopback_ristretto(issuer) redeemer = RistrettoRedeemer(treq, NOWHERE) random_tokens = redeemer.random_tokens_for_voucher( voucher, counter, num_tokens) d = redeemer.redeemWithCounter( voucher, counter, random_tokens, ) self.assertThat( d, failed( AfterPreprocessing( lambda f: f.value, Equals( UnexpectedResponse( INTERNAL_SERVER_ERROR, b"Sorry, this server does not behave well.", ), ), ), ), )
def test_redemption_denied_unpaid(self, voucher, counter, extra_tokens): """ If the issuer declines to allow the voucher to be redeemed and gives a reason that the voucher has not been paid for, ``RistrettoRedeem`` returns a ``Deferred`` that fires with a ``Failure`` wrapping ``Unpaid``. """ num_tokens = counter + extra_tokens issuer = UnpaidRedemption() treq = treq_for_loopback_ristretto(issuer) redeemer = RistrettoRedeemer(treq, NOWHERE) random_tokens = redeemer.random_tokens_for_voucher( voucher, counter, num_tokens) d = redeemer.redeemWithCounter( voucher, counter, random_tokens, ) self.assertThat( d, failed(AfterPreprocessing( lambda f: f.value, IsInstance(Unpaid), ), ), )
def test_bad_ristretto_redemption(self, voucher, counter, extra_tokens): """ If the issuer returns a successful result with an invalid proof then ``RistrettoRedeemer.redeem`` returns a ``Deferred`` that fires with a ``Failure`` wrapping ``SecurityException``. """ num_tokens = counter + extra_tokens signing_key = random_signing_key() issuer = RistrettoRedemption(signing_key) # Make it lie about the public key it is using. This causes the proof # to be invalid since it proves the signature was made with a # different key than reported in the response. issuer.public_key = PublicKey.from_signing_key(random_signing_key()) treq = treq_for_loopback_ristretto(issuer) redeemer = RistrettoRedeemer(treq, NOWHERE) random_tokens = redeemer.random_tokens_for_voucher( voucher, counter, num_tokens) d = redeemer.redeemWithCounter( voucher, counter, random_tokens, ) self.addDetail(u"redeem Deferred", text_content(str(d))) self.assertThat( d, failed( AfterPreprocessing( lambda f: f.value, IsInstance(SecurityException), ), ), )
def test_sync_other_issue_failure(self): """ When a sync is run and we try to issue a certificate for a domain but some non-ACME server error occurs, the sync should fail. """ self.fake_marathon.add_app({ 'id': '/my-app_1', 'labels': { 'HAPROXY_GROUP': 'external', 'MARATHON_ACME_0_DOMAIN': 'example.com' }, 'portDefinitions': [{ 'port': 9000, 'protocol': 'tcp', 'labels': {} }] }) self.txacme_client.issuance_error = RuntimeError('Something bad') d = self.marathon_acme.sync() assert_that( d, failed( MatchesStructure(value=MatchesStructure( subFailure=MatchesStructure(value=MatchesAll( IsInstance(RuntimeError), MatchesPredicate(str, 'Something bad'))))))) # Nothing stored, nothing notified assert_that(self.cert_store.as_dict(), succeeded(Equals({}))) assert_that(self.fake_marathon_lb.check_signalled_usr1(), Equals(False))
def test_single_fail(self): """ A single incoming message with a single file path. """ sut = MetadataExtractor('exiftool', ('-some', '-arg')) error = RuntimeError('boom!') sut.peer = Mock(spec=ExiftoolProtocol) sut.peer.execute.return_value = defer.fail(error) insert = { 'inserts': ['a'], 'deletes': [], 'data': { 'a': { 'path': '/path/to/file.jpg', } } } send = Mock(spec=Scheduler.send) result = sut(insert, send) self.assertEquals(send.call_count, 0) sut.peer.execute.assert_called_once_with( b'-some', b'-arg', b'-j', b'-charset', b'exiftool=UTF-8', b'-charset', b'filename=UTF-8', b'/path/to/file.jpg') failure_matcher = matchers.AfterPreprocessing( lambda f: f.value, matchers.IsInstance(MetadataExtractorError)) assert_that(result, twistedsupport.failed(failure_matcher))
def test_cap_not_okay(self, folder_name, collective_dircap, upload_dircap, token): """ If the response to a request for metadata about a capability for the magic folder does not receive an HTTP OK response, ``status`` fails with ``BadResponseCode``. """ tempdir = FilePath(self.mktemp()) node_directory = tempdir.child(u"node") node = self.useFixture(NodeDirectory(node_directory, token)) node.create_magic_folder( folder_name, collective_dircap, upload_dircap, tempdir.child(u"folder"), 60, ) # A bare resource will result in 404s for all requests made. That'll # do. treq = StubTreq(Resource()) self.assertThat( status(folder_name, node_directory, treq), failed( AfterPreprocessing( lambda f: f.value, IsInstance(BadResponseCode), ), ), )
def test_timer_errors(self): """ If the timed check fails (for example, because registration fails), the error should be caught and logged. """ with AcmeFixture(client=FailingClient()) as fixture: # Registration is triggered with service starts. fixture.service.startService() latest_logs = flush_logged_errors() self.assertThat(latest_logs, HasLength(1)) self.assertThat( str(latest_logs[0]), Contains('Failing at "register".')) # Forcing a check will trigger again the registration. self.assertThat( fixture.service._check_certs(), succeeded(Always())) latest_logs = flush_logged_errors() self.assertThat(latest_logs, HasLength(1)) self.assertThat( str(latest_logs[0]), Contains('Failing at "register".')) # Manually stop the service to not stop it from the fixture # and trigger another failure. self.assertThat( fixture.service.stopService(), failed(AfterPreprocessing( lambda f: f.value.args[0], Equals('Failing at "stop".')))) latest_logs = flush_logged_errors()
def test_add_writable_dmd(self, author, rw_collective_dircap, rw_upload_dircap, personal_dmd): """ Calling ``IParticipants.add`` with a read-write Personal DMD reports an error. """ assume(rw_collective_dircap != rw_upload_dircap) assume(rw_collective_dircap != personal_dmd) assume(rw_upload_dircap != personal_dmd) # we are testing error-cases, so don't need a real client participants = participants_from_collective( rw_collective_dircap, rw_upload_dircap, tahoe_client=None, ) self.assertThat( participants.add( create_local_author(author).to_remote_author(), personal_dmd, ), failed( AfterPreprocessing( lambda f: str(f.value), Equals( "New participant Personal DMD must be read-only dircap" ))))
def test_failed_node_connection(self, folder_name, collective_dircap, upload_dircap): """ If an HTTP request to the Tahoe-LAFS node fails, ``status`` returns a ``Deferred`` that fails with that failure. """ assume(collective_dircap != upload_dircap) tempdir = FilePath(self.mktemp()) node_directory = tempdir.child(u"node") node = self.useFixture(NodeDirectory(node_directory)) node.create_magic_folder( folder_name, collective_dircap, upload_dircap, tempdir.child(u"folder"), 60, ) exception = Exception("Made up failure") treq = HTTPClient(FailingAgent(Failure(exception))) self.assertThat( status(folder_name, node_directory, treq), failed(AfterPreprocessing( lambda f: f.value, Equals(exception), ), ), )
def test_fail_job(self): """ Tests that scheduler is stopped whenever a port is failing. """ port_out = object() port_in = Mock(spec=_port_callback, side_effect=RuntimeError('failed!')) self.flowmap[port_out] = port_in run_deferred = self.scheduler.run(self.clock) self.scheduler.send('some item', port_out) expected_message = 'Job failed on {:s} while processing {:s}'.format( str(port_in), 'some item') from testtools.twistedsupport._runtest import _NoTwistedLogObservers with _NoTwistedLogObservers(): with twistedsupport.CaptureTwistedLogs() as twisted_logs: # Trigger queue run. self.clock.advance(self.epsilon) assert_that(twisted_logs.getDetails(), matchers.MatchesDict({ 'twisted-log': matchers.AfterPreprocessing( lambda log: log.as_text(), matchers.Contains(expected_message)) })) port_in.assert_called_once_with('some item', self.scheduler.send) matcher = matchers.AfterPreprocessing(lambda f: f.value, matchers.IsInstance(RuntimeError)) assert_that(run_deferred, twistedsupport.failed(matcher))
def test_json_content_incorrect_content_type(self): """ When a request is made with the json_content callback and the content-type header is set to a value other than 'application/json' in the response headers then an error should be raised. """ d = self.cleanup_d(self.client.request('GET', path='/hello')) d.addCallback(json_content) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/hello'))) self.assertThat(request.requestHeaders, HasHeader('accept', ['application/json'])) request.setResponseCode(200) request.setHeader('Content-Type', 'application/octet-stream') request.write(json.dumps({}).encode('utf-8')) request.finish() yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, 'Expected header "Content-Type" to be "application/json" but ' 'found "application/octet-stream" instead')))
def test_sync_other_issue_failure(self): """ When a sync is run and we try to issue a certificate for a domain but some non-ACME server error occurs, the sync should fail. """ self.fake_marathon.add_app({ 'id': '/my-app_1', 'labels': { 'HAPROXY_GROUP': 'external', 'MARATHON_ACME_0_DOMAIN': 'example.com' }, 'portDefinitions': [ {'port': 9000, 'protocol': 'tcp', 'labels': {}} ] }) self.txacme_client.issuance_error = RuntimeError('Something bad') marathon_acme = self.mk_marathon_acme() d = marathon_acme.sync() assert_that(d, failed(MatchesStructure( value=MatchesStructure(subFailure=MatchesStructure( value=MatchesAll( IsInstance(RuntimeError), MatchesPredicate(str, 'Something bad') ) )) ))) # Nothing stored, nothing notified assert_that(self.cert_store.as_dict(), succeeded(Equals({}))) assert_that(self.fake_marathon_lb.check_signalled_usr1(), Equals(False))
def test_get_events_non_200(self): """ When a request is made to Marathon's event stream, and a non-200 response code is returned, an error should be raised. """ data = [] d = self.cleanup_d(self.client.get_events({'test': data.append})) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/v2/events'))) self.assertThat(request.requestHeaders, HasHeader('accept', ['text/event-stream'])) request.setResponseCode(202) request.setHeader('Content-Type', 'text/event-stream') json_data = {'hello': 'world'} request.write(b'event: test\n') request.write(b'data: %s\n' % (json.dumps(json_data).encode('utf-8'),)) request.write(b'\n') yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, 'Non-200 response code (202) for url: ' 'http://localhost:8080/v2/events'))) self.assertThat(data, Equals([])) request.finish() yield d
def test_json_content_missing_content_type(self): """ When a request is made with the json_content callback and the content-type header is not set in the response headers then an error should be raised. """ d = self.cleanup_d(self.client.request('GET', path='/hello')) d.addCallback(json_content) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/hello'))) self.assertThat(request.requestHeaders, HasHeader('accept', ['application/json'])) request.setResponseCode(200) # Twisted will set the content type to "text/html" by default but this # can be disabled by setting the default content type to None: # https://twistedmatrix.com/documents/current/api/twisted.web.server.Request.html#defaultContentType request.defaultContentType = None request.write(json.dumps({}).encode('utf-8')) request.finish() yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, 'Expected header "Content-Type" to be ' '"application/json" but header not found in response')))
def test_get_events_incorrect_content_type(self): """ When a request is made to Marathon's event stream, and the content-type header value returned is not "text/event-stream", an error should be raised. """ data = [] d = self.cleanup_d(self.client.get_events({'test': data.append})) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/v2/events'))) self.assertThat(request.requestHeaders, HasHeader('accept', ['text/event-stream'])) request.setResponseCode(200) request.setHeader('Content-Type', 'application/json') json_data = {'hello': 'world'} request.write(b'event: test\n') request.write(b'data: %s\n' % (json.dumps(json_data).encode('utf-8'),)) request.write(b'\n') yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, 'Expected header "Content-Type" to be "text/event-stream" but ' 'found "application/json" instead'))) self.assertThat(data, Equals([])) request.finish() yield d
def test_not_enough_tokens_for_retry(self, num_passes, extras): """ When there are not enough tokens to successfully complete a retry with the required number of passes, ``call_with_passes`` marks all passes reported as invalid during its efforts as such and resets all other passes it acquired. """ passes = pass_factory(integer_passes(num_passes + extras)) rejected = [] accepted = [] def reject_half_passes(group): num = len(group.passes) # Floor division will always short-change valid here, even for a # group size of 1. Therefore there will always be some passes # marked as invalid. accept_indexes = range(num // 2) reject_indexes = range(num // 2, num) # Only keep this iteration's accepted passes. We'll want to see # that the final iteration's passes are all returned. Passes from # earlier iterations don't matter. accepted[:] = list(group.passes[i] for i in accept_indexes) # On the other hand, keep *all* rejected passes. They should all # be marked as invalid and we want to make sure that's the case, # no matter which iteration rejected them. rejected.extend(group.passes[i] for i in reject_indexes) _ValidationResult( valid=accept_indexes, signature_check_failed=reject_indexes, ).raise_for(num) self.assertThat( call_with_passes( # Since half of every group is rejected, we'll eventually run # out of passes no matter how many we start with. reject_half_passes, num_passes, partial(passes.get, u"message"), ), failed( AfterPreprocessing( lambda f: f.value, IsInstance(NotEnoughTokens), ), ), ) self.assertThat( passes, MatchesStructure( # Whatever is left in the group when we run out of tokens must # be returned. returned=Equals(accepted), in_use=HasLength(0), invalid=AfterPreprocessing( lambda invalid: invalid.keys(), Equals(rejected), ), spent=HasLength(0), issued=Equals(set(accepted + rejected)), ), )
def test_version(self): """ Version is displayed with --version """ stdout = StringIO() stderr = StringIO() self.assertThat( dispatch_magic_folder_api_command( ["--version"], stdout=stdout, stderr=stderr, client=None, ), failed( AfterPreprocessing( lambda f: isinstance(f.value, SystemExit) and f.value.code, Equals(0) ) ) ) from .. import __version__ self.assertThat( stdout.getvalue().strip(), Equals("magic-folder-api version {}".format(__version__)) )
def test_pass_through_too_few_passes(self, num_passes): """ ``call_with_passes`` lets ``MorePassesRequired`` propagate through it if no passes have been marked as invalid. This happens if all passes given were valid but too fewer were given. """ passes = pass_factory() def reject_passes(passes): _ValidationResult( valid=range(len(passes)), signature_check_failed=[], ).raise_for(len(passes) + 1) self.assertThat( call_with_passes( reject_passes, num_passes, passes.get, ), failed( AfterPreprocessing( lambda f: f.value, Equals( MorePassesRequired( valid_count=num_passes, required_count=num_passes + 1, signature_check_failed=[], ), ), ), ), )
def test_async_error_propagates(self): """ If an unhandled asynchronous error occurs in an interceptor it propogates along the execution. """ interceptors = [tracer('a'), tracer('b'), thrower('c'), tracer('d')] self.assertThat(execute(empty_context, interceptors), failed(After(lambda f: f.type, Is(TracingError))))
def test_argument_serialization_failure(self): """ ``LocalRemote.callRemote`` returns a ``Deferred`` that fires with a failure if an argument cannot be serialized. """ ref = LocalRemote(DummyReferenceable(IHasSchema)) self.assertThat( ref.callRemote("whatever_method", BrokenCopyable()), failed(Always()), )
def test_sync_failure(self): """ When a sync is run and something fails, the failure is propagated to the sync's deferred. """ self.marathon_acme.marathon_client = MarathonClient( 'http://localhost:8080', client=failing_client) d = self.marathon_acme.sync() assert_that(d, failed(MatchesStructure(value=IsInstance(RuntimeError))))
def test_cancel_while_waiting_for_uptime(self): """ If the 'ready' deferred gets cancelled while still waiting for the minumum uptime, a proper message is emitted. """ self.protocol.makeConnection(self.process) self.protocol.ready.cancel() self.assertIn("minimum uptime not yet elapsed", self.logger.output) self.assertThat( self.protocol.ready, failed(MatchesStructure(value=IsInstance(CancelledError))))
def test_starting_stopping_cancellation(self): """ Test the starting and stopping behaviour. """ with AcmeFixture(client=HangingClient()) as fixture: d = fixture.service.when_certs_valid() self.assertThat(d, has_no_result()) fixture.service.startService() self.assertThat(d, has_no_result()) fixture.service.stopService() self.assertThat(d, failed(Always()))
def test_download_immutable_wrong_kind(self, cap): """ ``download_capability`` returns a ``Deferred`` that fails when a directory-capability is downloaded """ self.assertThat( self.tahoe_client.download_file(cap), failed( AfterPreprocessing( lambda failure: str(failure.value), Equals("{} is not a file capability".format(cap)))))
def test_cancel_ready(self): """ If the `ready` deferred gets cancelled, the protocol will stop doing anything related to waiting for the service to be ready. """ self.protocol.makeConnection(self.process) self.protocol.ready.cancel() self.assertThat( self.protocol.ready, failed(MatchesStructure(value=IsInstance(CancelledError)))) self.assertEqual(0, len(self.reactor.getDelayedCalls()))
def test_sync_failure(self): """ When a sync is run and something fails, the failure is propagated to the sync's deferred. """ marathon_acme = self.mk_marathon_acme() marathon_acme.marathon_client = MarathonClient( 'http://localhost:8080', client=failing_client) d = marathon_acme.sync() assert_that(d, failed(MatchesStructure( value=IsInstance(RuntimeError))))
def test_failure_during_request(self): """ When a failure occurs during a request, the exception is propagated to the request's deferred. """ client = self.get_client(failing_client) d = client.request('GET', path='/hello') self.assertThat(d, failed(MatchesStructure( value=IsInstance(RuntimeError)))) flush_logged_errors(RuntimeError)
def _api_error_test(self, operation): """ Assert that ``operation`` fails with ``TahoeAPIError``. """ self.assertThat( operation(), failed( AfterPreprocessing( lambda failure: failure.value, IsInstance(TahoeAPIError), ), ), )
def test_process_dies_shortly_after_fork(self): """ If the service process exists right after having been spawned (for example the executable was not found), the 'ready' Deferred fires with an errback. """ self.protocol.makeConnection(self.process) error = ProcessTerminated(exitCode=1, signal=None) self.protocol.processExited(Failure(error)) self.assertThat(self.protocol.ready, failed(MatchesStructure(value=Is(error))))
def test_snapshot_bad_metadata(self, raw_metadata): """ Test error-handling cases when de-serializing a snapshot. If the snapshot version is missing or wrong we should error. """ # arbitrary (but valid) content-cap contents = [] content_cap_d = self.tahoe_client.create_immutable(b"0" * 256) content_cap_d.addCallback(contents.append) self.assertThat(content_cap_d, succeeded(Always())) content_cap = contents[0] # invalid metadata cap (we use Hypothesis to give us two # definitely-invalid versions) metadata_caps = [] d = self.tahoe_client.create_immutable( json.dumps(raw_metadata).encode("utf8")) d.addCallback(metadata_caps.append) self.assertThat(d, succeeded(Always())) # create a Snapshot using the wrong metadata raw_snapshot_data = { u"content": format_filenode(content_cap), u"metadata": format_filenode( metadata_caps[0], { u"magic_folder": { u"author_signature": u"not valid", }, }, ), } snapshot_cap = [] d = self.tahoe_client.create_immutable_directory(raw_snapshot_data) d.addCallback(snapshot_cap.append) self.assertThat(d, succeeded(Always())) # now when we read back the snapshot with incorrect metadata, # it should fail snapshot_d = create_snapshot_from_capability(snapshot_cap[0], self.tahoe_client) self.assertThat( snapshot_d, failed( MatchesStructure(value=AfterPreprocessing( str, Contains("snapshot_version")), )))
def test_dispatch_with_fails(self): dispatcher = EventDispatcher() test_callback_prio_0_cb_0 = Mock() test_callback_prio_0_cb_1 = Mock(side_effect=RuntimeError('boom!')) test_callback_prio_1_cb_0 = Mock() dispatcher.add_listener(TestEvent, 0, test_callback_prio_0_cb_0) dispatcher.add_listener(TestEvent, 0, test_callback_prio_0_cb_1) dispatcher.add_listener(TestEvent, 1, test_callback_prio_1_cb_0) event = TestEvent() d = dispatcher.dispatch(event) matcher = matchers.AfterPreprocessing(lambda f: f.value, matchers.IsInstance(HandlerError)) assert_that(d, twistedsupport.failed(matcher))
def test_raise_exception(self, logger): """ An exception raised by the decorated function is passed through. """ class Result(Exception): pass @log_call_deferred(action_type=u"the-action") def f(): raise Result() self.assertThat( f(), failed( AfterPreprocessing( lambda f: f.value, IsInstance(Result), ), ), )
def test_malformed_error(self): """ Documint errors that do not have a JSON content type raise `MalformedDocumintError`. """ def _response_for(method, url, params, headers, data): return 400, {}, b'an error' resource = StringStubbingResource(_response_for) treq = StubTreq(resource) request = documint_request_factory(treq.request) self.assertThat( request(b'GET', b'http://example.com/malformed_error'), failed( AfterPreprocessing( lambda f: f.value, MatchesAll( IsInstance(MalformedDocumintError), MatchesStructure(data=Equals(b'an error'))))))
def test_store_unexpected_response(self): """ When the wrapped certificate store returns something other than None, an error should be raised as this is unexpected. """ class BrokenCertificateStore(object): def store(self, server_name, pem_objects): # Return something other than None return succeed('foo') mlb_store = MlbCertificateStore(BrokenCertificateStore(), self.client) d = mlb_store.store('example.com', EXAMPLE_PEM_OBJECTS) assert_that(d, failed(WithErrorTypeAndMessage( RuntimeError, "Wrapped certificate store returned something non-None. Don't " "know what to do with 'foo'.")))
def test_get_json_field_error(self): """ When get_json_field is used to make a request but the response code indicates an error, an HTTPError should be raised. """ d = self.cleanup_d( self.client.get_json_field('field-key', path='/my-path')) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/my-path'))) request.setResponseCode(404) request.write(b'Not found\n') request.finish() yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, '404 Client Error for url: %s' % self.uri('/my-path'))))
def test_sync_acme_server_failure_unacceptable(self): """ When a sync is run and we try to issue a certificate for a domain but the ACME server returns an error, if that error is of an unacceptable type then a failure should be returned. """ self.fake_marathon.add_app({ 'id': '/my-app_1', 'labels': { 'HAPROXY_GROUP': 'external', 'MARATHON_ACME_0_DOMAIN': 'example.com' }, 'portDefinitions': [ {'port': 9000, 'protocol': 'tcp', 'labels': {}} ] }) acme_error = acme_Error(typ='urn:acme:error:badCSR', detail='bar') # Server error takes an ACME error and a treq response...but we don't # have a response self.txacme_client.issuance_error = txacme_ServerError( acme_error, None) marathon_acme = self.mk_marathon_acme() d = marathon_acme.sync() # Oh god assert_that(d, failed(MatchesStructure( value=MatchesStructure(subFailure=MatchesStructure( value=MatchesAll(IsInstance(txacme_ServerError), MatchesStructure(message=MatchesAll( IsInstance(acme_Error), MatchesStructure( typ=Equals('urn:acme:error:badCSR'), detail=Equals('bar') ) )) ) )) ))) # Nothing stored, nothing notified assert_that(self.cert_store.as_dict(), succeeded(Equals({}))) assert_that(self.fake_marathon_lb.check_signalled_usr1(), Equals(False))
def test_server_error_response(self): """ When a request is made and the raise_for_status callback is added and a 5xx response code is returned, a HTTPError should be raised to indicate a server error. """ d = self.cleanup_d(self.client.request('GET', path='/hello')) d.addCallback(raise_for_status) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/hello'))) request.setResponseCode(502) request.write(b'Bad gateway\n') request.finish() yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, '502 Server Error for url: %s' % self.uri('/hello'))))
def test_request_fallback_all_failed(self): """ When we make a request and there are multiple Marathon endpoints specified, and all the endpoints fail, the last failure should be returned. """ agent = PerLocationAgent() agent.add_agent(b'localhost:8080', FailingAgent(RuntimeError('8080'))) agent.add_agent(b'localhost:9090', FailingAgent(RuntimeError('9090'))) client = MarathonClient( ['http://localhost:8080', 'http://localhost:9090'], client=treq_HTTPClient(agent)) d = self.cleanup_d(client.request('GET', path='/my-path')) yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( RuntimeError, '9090'))) flush_logged_errors(RuntimeError)
def test_not_json_error(self): """ Documint errors that have a JSON content type but do not contain valid JSON raise `MalformedDocumintError`. """ def _response_for(method, url, params, headers, data): return (400, {b'Content-Type': b'application/json'}, b'hello world') resource = StringStubbingResource(_response_for) treq = StubTreq(resource) request = documint_request_factory(treq.request) self.assertThat( request(b'GET', b'http://example.com/not_json_error'), failed( AfterPreprocessing( lambda f: f.value, MatchesAll( IsInstance(MalformedDocumintError), MatchesStructure(data=Equals(b'hello world'))))))
def test_get_json_field_missing(self): """ When get_json_field is used to make a request, the response is deserialized from JSON and if the specified field is missing, an error is raised. """ d = self.cleanup_d( self.client.get_json_field('field-key', path='/my-path')) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/my-path'))) json_response(request, {'other-field-key': 'do-not-care'}) yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( KeyError, '\'Unable to get value for "field-key" from Marathon response: ' '"{"other-field-key": "do-not-care"}"\'' )))
def test_get_json_field_incorrect_content_type(self): """ When get_json_field is used to make a request and the content-type header is set to a value other than 'application/json' in the response headers then an error should be raised. """ d = self.cleanup_d( self.client.get_json_field('field-key', path='/my-path')) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/my-path'))) request.setResponseCode(200) request.setHeader('Content-Type', 'application/octet-stream') request.write(json.dumps({}).encode('utf-8')) request.finish() yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, 'Expected header "Content-Type" to be "application/json" but ' 'found "application/octet-stream" instead')))
def test_error(self): """ Documint errors are parsed into a structured exception. """ def _response_for(method, url, params, headers, data): return (400, {b'Content-Type': b'application/json'}, json.dumps({u'causes': [ {u'type': u'foo', u'reason': 42, u'description': u'nope'}, {u'type': u'bar', u'reason': 42, u'description': None}, {u'type': u'baz', u'reason': None, u'description': None}]})) resource = StringStubbingResource(_response_for) treq = StubTreq(resource) request = documint_request_factory(treq.request) def cause(t, r=None, d=None): return MatchesStructure(type=Equals(t), reason=Equals(r), description=Equals(d)) self.assertThat( request(b'GET', b'http://example.com/error'), failed( AfterPreprocessing( lambda f: f.value, MatchesAll( IsInstance(DocumintError), MatchesStructure( causes=MatchesListwise([ cause(u'foo', 42, u'nope'), cause(u'bar', 42), cause(u'baz')]))))))
def test_request_failure(self): """ When the requests to all the marathon-lb instances have a bad status code then an error should be raised. """ d = self.cleanup_d(self.client.request('GET', path='/my-path')) for lb in ['lb1', 'lb2']: request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url='http://%s:9090/my-path' % (lb,))) request.setResponseCode(500) request.setHeader('content-type', 'text/plain') request.write(b'Internal Server Error') request.finish() yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( RuntimeError, 'Failed to make a request to all marathon-lb instances' ))) flush_logged_errors(HTTPError)
def fires_with_timeout(): """Assert that a notification request fails with a Timeout""" return failed( AfterPreprocessing(lambda f: f.value, IsInstance(Timeout)))
def fires_with_not_found(): """Assert that a notification request fails with a NotFound""" return failed( AfterPreprocessing(lambda f: f.value, IsInstance(NotFound)))
def fires_with_connection_closed(): """Assert that a notification request fails with a NotFound""" return failed( AfterPreprocessing(lambda f: f.value, IsInstance(ConnectionClosed)))