class TestAuthenticatedCORS(TestCase): def setUp(self): def check_cred(username, *args, **kwargs): return [username] @implementer(IAuthorizationPolicy) class AuthorizationPolicy(object): def permits(self, context, principals, permission): return permission in principals self.config = testing.setUp() self.config.include('cornice') self.config.add_route('noservice', '/noservice') self.config.set_authorization_policy(AuthorizationPolicy()) self.config.set_authentication_policy(BasicAuthAuthenticationPolicy( check_cred)) self.config.set_default_permission('readwrite') self.config.scan('cornice.tests.test_cors') self.app = TestApp(CatchErrors(self.config.make_wsgi_app())) def tearDown(self): testing.tearDown() def test_post_on_spam_should_be_forbidden(self): self.app.post('/spam', status=403) def test_preflight_does_not_need_authentication(self): self.app.options('/spam', status=200, headers={'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'POST'})
class TestAuthenticatedCORS(TestCase): def setUp(self): def check_cred(username, *args, **kwargs): return [username] @implementer(IAuthorizationPolicy) class AuthorizationPolicy(object): def permits(self, context, principals, permission): return permission in principals self.config = testing.setUp() self.config.include('cornice') self.config.add_route('noservice', '/noservice') self.config.set_authorization_policy(AuthorizationPolicy()) self.config.set_authentication_policy( BasicAuthAuthenticationPolicy(check_cred)) self.config.set_default_permission('readwrite') self.config.scan('cornice.tests.test_cors') self.app = TestApp(CatchErrors(self.config.make_wsgi_app())) def tearDown(self): testing.tearDown() def test_post_on_spam_should_be_forbidden(self): self.app.post('/spam', status=403) def test_preflight_does_not_need_authentication(self): self.app.options('/spam', status=200, headers={ 'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'POST' })
def test_collection_options(self): wsgiapp = self.config.make_wsgi_app() app = TestApp(wsgiapp) request = testing.DummyRequest() apply_request_extensions(request) self._fixture(request) headers = (('Access-Control-Request-Method', 'POST'), ('Origin', 'http://localhost')) app.options('/api/1/walls/2/collections', status=200, headers=headers)
def test_singular_resource(self, *a): View = get_test_view_class() config = _create_config() root = config.get_root_resource() root.add('thing', view=View) grandpa = root.add('grandpa', 'grandpas', view=View) wife = grandpa.add('wife', view=View, renderer='string') wife.add('child', 'children', view=View) config.begin() app = TestApp(config.make_wsgi_app()) self.assertEqual( '/grandpas/1/wife', route_path('grandpa:wife', testing.DummyRequest(), grandpa_id=1) ) self.assertEqual( '/grandpas/1', route_path('grandpa', testing.DummyRequest(), id=1) ) self.assertEqual( '/grandpas/1/wife/children/2', route_path('grandpa_wife:child', testing.DummyRequest(), grandpa_id=1, id=2) ) self.assertEqual(app.put('/grandpas').body, six.b('update_many')) self.assertEqual(app.head('/grandpas').body, six.b('')) self.assertEqual(app.options('/grandpas').body, six.b('')) self.assertEqual(app.delete('/grandpas/1').body, six.b('delete')) self.assertEqual(app.head('/grandpas/1').body, six.b('')) self.assertEqual(app.options('/grandpas/1').body, six.b('')) self.assertEqual(app.put('/thing').body, six.b('replace')) self.assertEqual(app.patch('/thing').body, six.b('update')) self.assertEqual(app.delete('/thing').body, six.b('delete')) self.assertEqual(app.head('/thing').body, six.b('')) self.assertEqual(app.options('/thing').body, six.b('')) self.assertEqual(app.put('/grandpas/1/wife').body, six.b('replace')) self.assertEqual(app.patch('/grandpas/1/wife').body, six.b('update')) self.assertEqual(app.delete('/grandpas/1/wife').body, six.b('delete')) self.assertEqual(app.head('/grandpas/1/wife').body, six.b('')) self.assertEqual(app.options('/grandpas/1/wife').body, six.b('')) self.assertEqual(six.b('show'), app.get('/grandpas/1').body) self.assertEqual(six.b('show'), app.get('/grandpas/1/wife').body) self.assertEqual( six.b('show'), app.get('/grandpas/1/wife/children/1').body)
def test_web_basic(): """The web basic test """ class TestService(Service): """The test service """ @get('/test/get') @post('/test/post') @put('/test/put') @delete('/test/delete') @head('/test/head') @patch('/test/patch') @options('/test/options') @endpoint() def test(self): """Test """ return 'OK' @get('/test2') @endpoint() def test2(self, param): """Test 2 """ return 'OK' adapter = WebAdapter() server = Server([ TestService() ], [ adapter ]) server.start() # Test app = TestApp(adapter) rsp = app.get('/test', expect_errors = True) assert rsp.status_int == 404 rsp = app.get('/test/get') assert rsp.status_int == 200 and rsp.content_type == 'text/plain' and rsp.text == 'OK' rsp = app.post('/test/post') assert rsp.status_int == 200 and rsp.content_type == 'text/plain' and rsp.text == 'OK' rsp = app.put('/test/put') assert rsp.status_int == 200 and rsp.content_type == 'text/plain' and rsp.text == 'OK' rsp = app.delete('/test/delete') assert rsp.status_int == 200 and rsp.content_type == 'text/plain' and rsp.text == 'OK' rsp = app.head('/test/head') assert rsp.status_int == 200 and rsp.content_type == 'text/plain' rsp = app.patch('/test/patch') assert rsp.status_int == 200 and rsp.content_type == 'text/plain' and rsp.text == 'OK' rsp = app.options('/test/options') assert rsp.status_int == 200 and rsp.content_type == 'text/plain' and rsp.text == 'OK' # Too many parameters rsp = app.get('/test/get?a=1', expect_errors = True) assert rsp.status_int == 400 # Lack of parameters rsp = app.get('/test2', expect_errors = True) assert rsp.status_int == 400 rsp = app.get('/test2?param=1') assert rsp.status_int == 200 and rsp.text == 'OK'
class PezWebAppTestCase(FreshSchemaTestCase): def __init__(self, *args, **kwargs): super(PezWebAppTestCase, self).__init__(*args, **kwargs) self.app = TestApp(application) def tearDown(self): super(PezWebAppTestCase, self).tearDown() self.app.reset() @parameterized.expand(route_response_map) def test_smoke(self, uri, result): response = self.app.get(uri) self.assertEqual(response.status, HTTP_OK) self.assertEqual(response.json, result) def test_shall_fail_on_le_zero_v2(self): response = self.app.get( '/v2/forward?count=0', expect_errors=True ) self.assertEqual(response.status, HTTP_BAD_REQUEST) def test_shall_fail_on_gt_max_count_v2(self): max_count = configuration.get('max_count') response = self.app.get( '/v2/forward?count={0}'.format(max_count + 1), expect_errors=True ) self.assertEqual(response.status, HTTP_BAD_REQUEST) def test_shall_fail_with_404(self): response = self.app.get('/WRONG_BANANAS', expect_errors=True) self.assertEqual(response.status, HTTP_NOT_FOUND) def test_shall_return_help_on_options(self): response = self.app.options('/v2/forward') self.assertEqual(response.status, HTTP_OK) self.assertIn(u'description', response.json) self.assertIn(u'verbs', response.json) def test_shall_return_503_on_dead_db(self): with patch('sqlalchemy.orm.session.Session.execute') as mock_exec: mock_exec.side_effect = DBAPIError('Boom', 'mock', 'mock') response = self.app.get( '/v1/forward', expect_errors=True ) self.assertEqual(response.status, HTTP_SERVICE_UNAVAILABLE)
def test_option_handler(self): def raises_app(environ, start_response): raise IOError('bad') option_app = cors_option_filter_factory(raises_app) app = cors_filter_factory(option_app) testapp = TestApp(app) res = testapp.options('/the_path_doesnt_matter', extra_environ={ 'HTTP_ORIGIN': 'http://example.org'}, status=200) assert_that(res.headers, has_key('Access-Control-Allow-Methods')) # Non-options pass through res = testapp.get('/', extra_environ={ 'HTTP_ORIGIN': 'http://example.org'}, status=500) assert_that(res.headers, has_key('Access-Control-Allow-Origin'))
class RouterTestCase(unittest.TestCase): def __init__(self, methodName='runTest'): super().__init__(methodName) def setRouter(self, app): self._app = TestApp(app.make_handler()) def get(self, path_info, headers=[]): resp = self._app.get(path_info, headers=headers, status='*') return It(resp, self) def options(self, path_info, headers=[]): resp = self._app.options(path_info, headers=headers, status='*') return It(resp, self) def delete(self, path_info, headers=[]): resp = self._app.delete(path_info, headers=headers, status='*') return It(resp, self) def post(self, path_info, json=None, headers=[]): if json: resp = self._app.post_json(path_info, json, headers=headers, status='*') return It(resp, self)
class TestCorkscrew(unittest.TestCase): def setUp(self): database.initialize(SqliteDatabase(":memory:")) insertFixtures() app = CorkscrewApplication(PHF) app.register(Comment, endpoint="/comments") app.register(Person, related={"articles": Article}, endpoint="/people") app.register(Photo, related={"tags": Link(Tag, via=PhotoTag)}, endpoint="/photos") app.register(Article, related={ "comments": Comment, "revisions": Link(Revision, on="parent") }, endpoint="/articles") self.app = TestApp(app) def tearDown(self): database.close() def testList(self): result = self.app.get("/articles") self.assertEqual(result.status, "200 OK") JsonAPIValidator.validate_content_type(result.content_type) self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) self.assertIn("data", result.json) self.assertIs(len(result.json["data"]), len(ARTICLE_TITLES)) for row in result.json["data"]: self.assertEqual(row["type"], "article") self.assertIn("attributes", row) # expected attributes: title, created self.assertIs(len(row["attributes"]), 2) self.assertIn("title", row["attributes"]) self.assertIn("created", row["attributes"]) self.assertNotIn("author", row["attributes"]) self.assertIn(row["attributes"]["title"], ARTICLE_TITLES) self.assertIn("relationships", row) for key, relationship in row["relationships"].iteritems(): self.assertIn(key, ["comments", "cover", "author", "revisions"]) self.assertIn("links", relationship) self.assertIn("related", relationship["links"]) self.assertIn("self", relationship["links"]) def testGet(self): result = self.app.get("/articles/1") self.assertEqual(result.status, "200 OK") self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) self.assertIn("data", result.json) # we want a single result self.assertEqual(type(result.json["data"]), type({})) self.assertIn("attributes", result.json["data"]) attributes = result.json["data"]["attributes"] self.assertEqual(attributes["title"], ARTICLE_TITLES[0]) self.assertIn("relationships", result.json["data"]) for key, rel in result.json["data"]["relationships"].iteritems(): self.assertIn(key, ["comments", "cover", "author", "revisions"]) self.assertIn("links", rel) self.assertIn("related", rel["links"]) self.assertIn("self", rel["links"]) self.assertIsInstance( result.json["data"]["relationships"]["comments"]["data"], list) def testPost(self): request = { u"data": { u"type": u"article", u"attributes": { u"title": u"Test entry" }, u"relationships": { u"author": { u"data": { u"id": u"1", u"type": u"person" } } } } } JsonAPIValidator.validate_jsonapi(request, True) result = self.app.post_json("/articles", params=request) self.assertEqual(result.status, "200 OK") JsonAPIValidator.validate_content_type(result.content_type) self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) self.assertIn("relationships", result.json["data"]) for key, rel in result.json["data"]["relationships"].iteritems(): self.assertIn(key, ["comments", "cover", "author", "revisions"]) self.assertIn("links", rel) self.assertIn("related", rel["links"]) self.assertIn("self", rel["links"]) def testPatch(self): request = { u"data": { u"type": u"article", u"id": u"1", u"attributes": { u"title": u"Changed First Entry" } } } JsonAPIValidator.validate_jsonapi(request) result = self.app.patch_json("/articles/1", params=request) self.assertIn(result.status, ["202 Accepted", "200 OK", "204 No Content"]) if result.status == "204 No Content": # nothing more to test return JsonAPIValidator.validate_content_type(result.content_type) self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) self.assertEqual(result.json["data"]["attributes"]["title"], "Changed First Entry") def testDelete(self): result = self.app.delete("/articles/1") self.assertIn(result.status, ["202 Accepted", "204 No Content", "200 OK"]) def testGetRelated(self): result = self.app.get("/articles/1") self.assertEqual(result.status, "200 OK") JsonAPIValidator.validate_content_type(result.content_type) self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) result = self.app.get( result.json["data"]["relationships"]["author"]["links"]["related"]) self.assertEqual(result.status, "200 OK") JsonAPIValidator.validate_content_type(result.content_type) self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) self.assertIn("data", result.json) self.assertEqual(type(result.json["data"]), type({})) self.assertEqual(result.json["data"]["type"], "person") self.assertEqual(result.json["data"]["id"], "1") def testGetRelationship(self): result = self.app.get("/articles/1") self.assertEqual(result.status, "200 OK") JsonAPIValidator.validate_content_type(result.content_type) self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) result = self.app.get( result.json["data"]["relationships"]["author"]["links"]["self"]) self.assertEqual(result.status, "200 OK") JsonAPIValidator.validate_content_type(result.content_type) self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) self.assertIn("data", result.json) self.assertIsInstance(result.json["data"], dict) self.assertEqual(result.json["data"]["type"], "person") self.assertEqual(result.json["data"]["id"], "1") def testPatchRelationship(self): result = self.app.get("/articles/1") self.assertEqual(result.status, "200 OK") JsonAPIValidator.validate_content_type(result.content_type) self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) rel = result.json["data"]["relationships"]["author"]["links"]["self"] request = {u"data": {u"type": u"person", u"id": u"2"}} JsonAPIValidator.validate_jsonapi(request) result = self.app.patch_json(rel, params=request) self.assertIn(result.status, ["200 OK", "202 Accepted", "204 No Content"]) if result.status == "204 No Content": self.assertIs(len(result.body), 0) elif result.status == "200 OK": self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) def testFetchingDataCollection(self): result = self.app.get("/articles") JsonAPIValidator.validate_content_type(result.content_type) self.assertEqual(result.status, "200 OK") JsonAPIValidator.validate_jsonapi(result.json) self.assertIs(len(result.json["data"]), 2) for entry in result.json["data"]: self.assertEqual(entry["type"], "article") self.assertIsInstance(entry["id"], unicode) self.assertIn(entry["attributes"]["title"], ARTICLE_TITLES) Article.delete().where(True).execute() result = self.app.get("/articles") JsonAPIValidator.validate_content_type(result.content_type) self.assertEqual(result.status, "200 OK") self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) self.assertIs(len(result.json["data"]), 0) def testFetchingNullRelationship(self): result = self.app.get("/articles/1") rel = result.json["data"]["relationships"]["cover"]["links"]["related"] result = self.app.get(rel) JsonAPIValidator.validate_content_type(result.content_type) self.assertEqual(result.status, "200 OK") self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) self.assertIsNone(result.json["data"]) def testFetchingMissingSingleResource(self): result = self.app.get("/article/1337", status=404) self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) def testCreatingResourceWithReferences(self): request = { u"data": { u"type": u"photo", u"attributes": { u"title": u"Ember Hamster", u"src": u"http://example.com/images/productivity.png" }, u"relationships": { u"photographer": { u"data": { u"type": u"people", u"id": u"2" } } } } } JsonAPIValidator.validate_jsonapi(request, True) result = self.app.post_json("/photos", params=request) JsonAPIValidator.validate_content_type(result.content_type) JsonAPIValidator.validate_jsonapi(result.json) if not result.location: warnings.warn( "The response SHOULD include a Location header identifying the" "location of the newly created resource.") else: res = self.app.get(result.location) self.assertIsNotNone(res.json) JsonAPIValidator.validate_jsonapi(res.json) def testCreatingResourceWithMissingRequiredAttributeShouldFail(self): request = { u"data": { u"type": u"person", u"attributes": { u"name": u"Eve Bobbington" # attribute 'age' is missing } } } JsonAPIValidator.validate_jsonapi(request, True) result = self.app.post_json("/people", params=request, status=400) JsonAPIValidator.validate_content_type(result.content_type) JsonAPIValidator.validate_jsonapi(result.json) def testCreateResourceWithAlreadyExistingId(self): request = { u"data": { u"type": u"person", u"id": u"1", u"attributes": { u"name": "Jimmy Cricket", u"age": 12 } } } JsonAPIValidator.validate_jsonapi(request) # expect this to fail result = self.app.post_json("/people", params=request, status=409) JsonAPIValidator.validate_jsonapi(result.json) def testUpdatingResourceViaSelfLink(self): UPDATE_TITLE = u"Five Ways You Have Never Tried To Access Your Data" result = self.app.get("/articles/1") update_uri = result.json["data"]["links"]["self"] request = { u"data": { u"type": u"article", u"id": u"1", u"attributes": { u"title": UPDATE_TITLE } } } JsonAPIValidator.validate_jsonapi(request) res = self.app.patch_json(update_uri, params=request) if "204" not in res.status: JsonAPIValidator.validate_content_type(res.content_type) res = self.app.get("/articles/1") self.assertEqual(res.json["data"]["attributes"]["title"], UPDATE_TITLE) def testUpdatingResourceRelationships(self): result = self.app.get("/articles/1") request = result.json # Person(1) is the current author self.assertEqual( result.json["data"]["relationships"]["author"]["data"]["id"], "1") # don't update attributes, server must ignore missing attributes del request["data"]["attributes"] # do not update the 'comments' and 'cover' relationships del request["data"]["relationships"]["comments"] del request["data"]["relationships"]["cover"] del request["data"]["relationships"]["revisions"] ptype = request["data"]["relationships"]["author"]["data"]["type"] # change author to Person(2) request["data"]["relationships"]["author"] = { u"data": { u"id": u"2", u"type": ptype } } JsonAPIValidator.validate_jsonapi(request) result = self.app.patch_json("/articles/1", params=request) result = self.app.get("/articles/1") self.assertNotEqual( result.json["data"]["relationships"]["author"]["data"]["id"], "1") self.assertEqual( result.json["data"]["relationships"]["author"]["data"]["id"], "2") self.assertEqual(result.json["data"]["attributes"]["title"], ARTICLE_TITLES[0]) def testDeletingIndividualResource(self): result = self.app.get("/photos/1") JsonAPIValidator.validate_jsonapi(result.json) result = self.app.delete("/photos/1") if result.status_int not in [202, 204, 200]: warnings.warn("Delete: A server MAY respond with other HTTP status" "codes. This code is unknown to the specification.") if result.status_int == 200: JsonAPIValidator.validate_jsonapi(result.json) # the resource should be gone now self.app.get("/photos/1", status=404) def testFetchingRelatedOneToNResource(self): result = self.app.get("/articles/1/comments") JsonAPIValidator.validate_jsonapi(result.json) for entry in result.json["data"]: self.assertIn(entry["attributes"]["body"], COMMENT_BODIES) self.assertEqual(entry["relationships"]["author"]["data"]["id"], "2") self.assertEqual(entry["relationships"]["article"]["data"]["id"], "1") def testListingRelatedOneToNResource(self): result = self.app.get("/articles/1/relationships/comments") JsonAPIValidator.validate_jsonapi(result.json) for entry in result.json["data"]: JsonAPIValidator.validate_resource_identifier(entry) def testPatchingRelatedOneToNResourceShouldFail(self): result = self.app.get("/people/1/articles") self.assertIs(len(result.json["data"]), 2) for entry in result.json["data"]: self.assertIn(entry["attributes"]["title"], ARTICLE_TITLES) # it is not allowed to orphan an article request = { u"data": { u"id": u"1", u"type": u"person", u"relationships": { u"articles": { u"data": [] } } } } JsonAPIValidator.validate_jsonapi(request) result = self.app.patch_json("/people/1", params=request, status=400) JsonAPIValidator.validate_jsonapi(result.json) self.assertIn("orphan", result.json["errors"][0]["title"]) def testPatchingRelatedOneToNResourceShouldSucceed(self): result = self.app.get("/articles/2/cover") self.assertIsInstance(result.json["data"], dict) request = { u"data": { u"id": u"2", u"type": u"article", u"relationships": { u"cover": { u"data": None } } } } JsonAPIValidator.validate_jsonapi(request) result = self.app.patch_json("/articles/2", params=request) result = self.app.get("/articles/2/cover") self.assertIsNone(result.json["data"]) def testPatchingRelatedOneToMResource(self): result = self.app.get("/articles/1/relationships/comments") self.assertIsInstance(result.json["data"], list) del result.json["data"][0] request = {u"data": result.json["data"]} JsonAPIValidator.validate(request) result = self.app.patch_json("/articles/1/relationships/comments", params=request) def testPatchingRelatedNToMResource(self): result = self.app.get("/photos/1/relationships/tags") self.assertIsInstance(result.json["data"], list) self.assertIs(len(result.json["data"]), 2) request = {u"data": result.json["data"]} # remove one tag request["data"].pop() self.app.patch_json("/photos/1/relationships/tags", params=request) result = self.app.get("/photos/1/relationships/tags") self.assertIsInstance(result.json["data"], list) self.assertIs(len(result.json["data"]), 1) def testGetNToMRelationship(self): result = self.app.get("/photos/1/tags") JsonAPIValidator.validate(result.json) self.assertIs(len(result.json["data"]), 2) for tag in result.json["data"]: self.assertIn(tag["attributes"]["name"], TAG_NAMES) def testValidateForwardRelationship(self): result = self.app.get("/photos") # Photo has a forward relationship to Person (photographer) for entry in result.json["data"]: self.assertIn("relationships", entry) for name, relationship in entry["relationships"].iteritems(): self.assertIn(name, ["photographer", "tags"]) relationship = entry["relationships"]["photographer"] data = relationship["data"] # retrieve the /relationships link subresult = self.app.get(relationship["links"]["self"]) self.assertEqual(subresult.json["data"], data) # retrieve the related object subresult = self.app.get(relationship["links"]["related"]) # type and id must match self.assertEqual(subresult.json["data"]["id"], data["id"]) self.assertEqual(subresult.json["data"]["type"], data["type"]) # retrieve the related self link and test if it's the same object subsubresult = self.app.get( subresult.json["data"]["links"]["self"]) self.assertEqual(subresult.json["data"], subsubresult.json["data"]) def testValidateReverseRelationships(self): result = self.app.get("/photos") # Photo has a reverse relationship to Tag (tags, via PhotoTag) for entry in result.json["data"]: self.assertIn("relationships", entry) for name, relationship in entry["relationships"].iteritems(): self.assertIn(name, ["photographer", "tags"]) relationship = entry["relationships"]["tags"] data = relationship["data"] # retrieve the /relationships link subresult = self.app.get(relationship["links"]["self"]) self.assertEqual(subresult.json["data"], data) # retrieve the related objects subresult = self.app.get(relationship["links"]["related"]) # type and id must match for subentry in subresult.json["data"]: self.assertIn({ "id": subentry["id"], "type": subentry["type"] }, data) if "links" in subentry and "self" in subentry["links"]: subsubresult = self.app.get(subentry["links"]["self"]) self.assertEqual(subentry, subsubresult.json["data"]) def testIncludeParameterForwardRelationship(self): result = self.app.get("/articles/2?include=cover") JsonAPIValidator.validate(result.json) self.assertIn("included", result.json) # the server must not return any other fields than requested self.assertIs(len(result.json["included"]), 1) ref = result.json["data"]["relationships"]["cover"]["data"] inc = result.json["included"][0] self.assertEqual(inc["type"], ref["type"]) self.assertEqual(inc["id"], ref["id"]) # the self link must be valid and refer to the same object subresult = self.app.get(inc["links"]["self"]) self.assertEqual(subresult.json["data"], inc) def testIncludeParameterReverseRelationship(self): result = self.app.get("/articles/1?include=comments") JsonAPIValidator.validate(result.json) self.assertIn("included", result.json) # the server must not return any other fields than requested self.assertIs(len(result.json["included"]), len(COMMENT_BODIES)) refs = result.json["data"]["relationships"]["comments"]["data"] for inc in result.json["included"]: self.assertIn({"id": inc["id"], "type": inc["type"]}, refs) # the self link must be valid and refer to the same object subresult = self.app.get(inc["links"]["self"]) self.assertEqual(subresult.json["data"], inc) self.assertEqual(subresult.json["links"]["self"], inc["links"]["self"]) def testIncludeParameterWithInvalidFields(self): self.app.get("/articles/1?include=invalid-field", status=400) self.app.get("/articles/1?include=author,invalid-field", status=400) def testIncludeParameterWithCircularRelationships(self): self.app.get("/articles/1?include=comments.articles", status=400) self.app.get("/articles/1?include=comments.articles.comments", status=400) def testSparseFieldsets(self): result = self.app.get("/people/1?fields[person]=age") JsonAPIValidator.validate(result.json) self.assertNotIn("name", result.json["data"]["attributes"]) self.assertIn("age", result.json["data"]["attributes"]) def testSparseFieldsetsWithIncludedObjects(self): result = self.app.get("/articles/1?include=comments&fields[comment]=") JsonAPIValidator.validate(result.json) for inc in result.json["included"]: self.assertNotIn("attributes", inc) result = self.app.get( "/comments/1?include=article.author&fields[person]=age") JsonAPIValidator.validate(result.json) is_included = False for inc in result.json["included"]: if inc["type"] == "person": is_included = True self.assertIn("age", inc["attributes"]) self.assertNotIn("name", inc["attributes"]) self.assertIsNotNone(is_included) def testLinkWithSpecifiedField(self): result = self.app.get("/articles/1/relationships/revisions") JsonAPIValidator.validate(result.json) def testOPTIONSRequest(self): # all of these should return all 200 OK code self.app.options("/articles") self.app.options("/articles/1") self.app.options("/articles/1/cover") self.app.options("/articles/1/relationships/cover") self.app.options("/photos/2/tags") self.app.options("/articles/1?include=author") def testCORSHeaders(self): # simulate a browser result = self.app.options("/articles", headers={ "Origin": "http://example.org", "Access-Control-Request-Method": "GET", "Access-Control-Request-Headers": "X-Requested-With" }) self.assertIn("Access-Control-Allow-Origin", result.headers) self.assertIn("Access-Control-Allow-Methods", result.headers) self.assertEqual(result.headers["Access-Control-Allow-Origin"], "*") methods = map( lambda x: x.strip(), result.headers["Access-Control-Allow-Methods"].split(",")) self.assertIn("GET", methods) self.assertIn("X-Requested-With".lower(), result.headers["Access-Control-Allow-Headers"].lower())
class TestAppMethods(unittest.TestCase): def setUp(self): self.app = TestApp(app) def test_get_index(self): resp = self.app.get('/') self.assertEqual('200 OK', resp.status) resp.mustcontain('<button>submit</button>') resp.mustcontain(no='<h2>Duplicated!!!</h2>') resp.mustcontain(no='<h2>Saved</h2>') def test_post_data_to_index(self): data = uid() for status in ['save', 'duplicated']: form = self.app.get('/').form form['data'] = data resp = form.submit() self.assertEqual('200 OK', resp.status) resp.mustcontain('<button>submit</button>') if status == 'save': resp.mustcontain('<h2>Saved</h2>') resp.mustcontain(no='<h2>Duplicated!!!</h2>') else: resp.mustcontain(no='<h2>Saved</h2>') resp.mustcontain('<h2>Duplicated!!!</h2>') def test_add_allow_origin(self): resp = self.app.get('/') self.assertEqual(('Access-Control-Allow-Origin', '*'), resp.headerlist[0]) resp = self.app.post( '/api/exist', content_type='application/json', params=json.dumps({ 'data': ['a', 'b'] }) ) self.assertEqual(('Access-Control-Allow-Origin', '*'), resp.headerlist[1]) def test_options_work(self): resp = self.app.options('/') self.assertEqual('200 OK', resp.status) self.assertEqual({}, resp.json) def test_api_exist_work(self): data = [uid(), uid(), uid()] form = self.app.get('/').form form['data'] = data[0] resp = form.submit() self.assertEqual('200 OK', resp.status) form['data'] = data[2] resp = form.submit() self.assertEqual('200 OK', resp.status) resp = self.app.post( '/api/exist', content_type='application/json', params=json.dumps({ 'data': data }) ) self.assertEqual({'data': [1, 0, 1]}, resp.json) def test_api_exist_substring(self): data = ['1234', '123'] form = self.app.get('/').form form['data'] = data[0] resp = form.submit() self.assertEqual('200 OK', resp.status) resp = self.app.post( '/api/exist', content_type='application/json', params=json.dumps({ 'data': data }) ) self.assertEqual({'data': [1, 1]}, resp.json) def test_api_post(self): data = [uid(), uid(), uid()] resp = self.app.post( '/api/post', content_type='application/json', params=json.dumps({ 'data': data }) ) self.assertEqual('200 OK', resp.status) resp = self.app.post( '/api/exist', content_type='application/json', params=json.dumps({ 'data': data }) ) self.assertEqual({'data': [1, 1, 1]}, resp.json)
class TestCORS(TestCase): def setUp(self): self.config = testing.setUp() self.config.include("cornice") self.config.add_route('noservice', '/noservice') self.config.scan("cornice.tests.test_cors") self.app = TestApp(CatchErrors(self.config.make_wsgi_app())) def tearDown(self): testing.tearDown() def test_preflight_cors_klass_post(self): resp = self.app.options('/cors_klass', status=200, headers={ 'Origin': 'lolnet.org', 'Access-Control-Request-Method': 'POST'}) def test_preflight_cors_klass_put(self): resp = self.app.options('/cors_klass', status=400, headers={ 'Origin': 'lolnet.org', 'Access-Control-Request-Method': 'PUT'}) def test_preflight_missing_headers(self): # we should have an OPTION method defined. # If we just try to reach it, without using correct headers: # "Access-Control-Request-Method"or without the "Origin" header, # we should get a 400. resp = self.app.options('/squirel', status=400) self.assertEqual(len(resp.json['errors']), 2) def test_preflight_missing_origin(self): resp = self.app.options( '/squirel', headers={'Access-Control-Request-Method': 'GET'}, status=400) self.assertEqual(len(resp.json['errors']), 1) def test_preflight_missing_request_method(self): resp = self.app.options( '/squirel', headers={'Origin': 'foobar.org'}, status=400) self.assertEqual(len(resp.json['errors']), 1) def test_preflight_incorrect_origin(self): # we put "lolnet.org" where only "notmyidea.org" is authorized resp = self.app.options( '/squirel', headers={'Origin': 'lolnet.org', 'Access-Control-Request-Method': 'GET'}, status=400) self.assertEqual(len(resp.json['errors']), 1) def test_preflight_correct_origin(self): resp = self.app.options( '/squirel', headers={'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'GET'}) self.assertEqual( resp.headers['Access-Control-Allow-Origin'], 'notmyidea.org') allowed_methods = (resp.headers['Access-Control-Allow-Methods'] .split(',')) self.assertNotIn('POST', allowed_methods) self.assertIn('GET', allowed_methods) self.assertIn('PUT', allowed_methods) self.assertIn('HEAD', allowed_methods) allowed_headers = (resp.headers['Access-Control-Allow-Headers'] .split(',')) self.assertIn('X-My-Header', allowed_headers) self.assertNotIn('X-Another-Header', allowed_headers) def test_preflight_deactivated_method(self): self.app.options('/squirel', headers={'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'POST'}, status=400) def test_preflight_origin_not_allowed_for_method(self): self.app.options('/squirel', headers={'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'PUT'}, status=400) def test_preflight_credentials_are_supported(self): resp = self.app.options('/spam', headers={'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'GET'}) self.assertIn('Access-Control-Allow-Credentials', resp.headers) self.assertEqual(resp.headers['Access-Control-Allow-Credentials'], 'true') def test_preflight_credentials_header_not_included_when_not_needed(self): resp = self.app.options('/spam', headers={'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'POST'}) self.assertNotIn('Access-Control-Allow-Credentials', resp.headers) def test_preflight_contains_max_age(self): resp = self.app.options('/spam', headers={'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'GET'}) self.assertIn('Access-Control-Max-Age', resp.headers) self.assertEqual(resp.headers['Access-Control-Max-Age'], '42') def test_resp_dont_include_allow_origin(self): resp = self.app.get('/squirel') # omit the Origin header self.assertNotIn('Access-Control-Allow-Origin', resp.headers) self.assertEqual(resp.json, 'squirels') def test_responses_include_an_allow_origin_header(self): resp = self.app.get('/squirel', headers={'Origin': 'notmyidea.org'}) self.assertIn('Access-Control-Allow-Origin', resp.headers) self.assertEqual(resp.headers['Access-Control-Allow-Origin'], 'notmyidea.org') def test_credentials_are_included(self): resp = self.app.get('/spam', headers={'Origin': 'notmyidea.org'}) self.assertIn('Access-Control-Allow-Credentials', resp.headers) self.assertEqual(resp.headers['Access-Control-Allow-Credentials'], 'true') def test_headers_are_exposed(self): resp = self.app.get('/squirel', headers={'Origin': 'notmyidea.org'}) self.assertIn('Access-Control-Expose-Headers', resp.headers) headers = resp.headers['Access-Control-Expose-Headers'].split(',') self.assertIn('X-My-Header', headers) def test_preflight_request_headers_are_included(self): resp = self.app.options('/squirel', headers={'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'GET', 'Access-Control-Request-Headers': 'foo, bar,baz '}) # The specification says we can have any number of LWS (Linear white # spaces) in the values and that it should be removed. # per default, they should be authorized, and returned in the list of # authorized headers headers = resp.headers['Access-Control-Allow-Headers'].split(',') self.assertIn('foo', headers) self.assertIn('bar', headers) self.assertIn('baz', headers) def test_preflight_request_headers_isnt_too_permissive(self): self.app.options('/eggs', headers={'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'GET', 'Access-Control-Request-Headers': 'foo,bar,baz'}, status=400) def test_preflight_headers_arent_case_sensitive(self): self.app.options('/spam', headers={ 'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'GET', 'Access-Control-Request-Headers': 'x-my-header', }) def test_400_returns_CORS_headers(self): resp = self.app.get('/bacon/not', status=400, headers={'Origin': 'notmyidea.org'}) self.assertIn('Access-Control-Allow-Origin', resp.headers) def test_404_returns_CORS_headers(self): resp = self.app.get('/bacon/notgood', status=404, headers={'Origin': 'notmyidea.org'}) self.assertIn('Access-Control-Allow-Origin', resp.headers) def test_existing_non_service_route(self): resp = self.app.get('/noservice', status=200, headers={'Origin': 'notmyidea.org'}) self.assertEqual(resp.body, b'No Service here.')
class AppUser: """:class:`webtest.TestApp` wrapper for backend functional testing.""" def __init__(self, app, rest_url: str = 'http://localhost', base_path: str = '/', header: dict = None): """Initialize self.""" self.app = TestApp(app) """:class:`webtest.TestApp`to send requests to the backend server.""" self.rest_url = rest_url """backend server url to generate request urls.""" self.base_path = base_path """path prefix to generate request urls.""" self.header = header or {} """default header for requests, mostly for authentication.""" self._resolver = DottedNameResolver() def post_resource(self, path: str, iresource: IInterface, cstruct: dict) -> TestResponse: """Build and post request to create a new resource.""" url = self._build_url(path) props = self._build_post_body(iresource, cstruct) resp = self.app.post_json(url, props, headers=self.header, expect_errors=True) return resp def put(self, path: str, cstruct: dict = {}) -> TestResponse: """Put request to modify a resource.""" url = self._build_url(path) resp = self.app.put_json(url, cstruct, headers=self.header, expect_errors=True) return resp def post(self, path: str, cstruct: dict = {}) -> TestResponse: """Post request to create a new resource.""" url = self._build_url(path) resp = self.app.post_json(url, cstruct, headers=self.header, expect_errors=True) return resp def _build_post_body(self, iresource: IInterface, cstruct: dict) -> dict: return {'content_type': iresource.__identifier__, 'data': cstruct} def _build_url(self, path: str) -> str: if path.startswith('http'): return path return self.rest_url + self.base_path + path def batch(self, subrequests: list): """Build and post batch request to the backend rest server.""" resp = self.app.post_json(batch_url, subrequests, headers=self.header, expect_errors=True) return resp def get(self, path: str, params={}) -> TestResponse: """Send get request to the backend rest server.""" url = self._build_url(path) resp = self.app.get(url, headers=self.header, params=params, expect_errors=True) return resp def options(self, path: str) -> TestResponse: """Send options request to the backend rest server.""" url = self._build_url(path) resp = self.app.options(url, headers=self.header, expect_errors=True) return resp def get_postable_types(self, path: str) -> []: """Send options request and return the postable content types.""" resp = self.options(path) if 'POST' not in resp.json: return [] post_request_body = resp.json['POST']['request_body'] type_names = sorted([r['content_type'] for r in post_request_body]) iresources = [self._resolver.resolve(t) for t in type_names] return iresources
class TestHttplib(unittest.TestCase): client = 'httplib' client_options = {} def setUp(self): self.server = StopableWSGIServer.create(debug_app) self.application_url = self.server.application_url.rstrip('/') self.proxy = proxies.HostProxy(self.application_url, client=self.client, **self.client_options) self.app = TestApp(self.proxy) def test_form(self): resp = self.app.get('/form.html') resp.mustcontain('</form>') form = resp.form form['name'] = 'gawel' resp = form.submit() resp.mustcontain('name=gawel') def test_head(self): resp = self.app.head('/form.html') self.assertEqual(resp.status_int, 200) self.assertEqual(len(resp.body), 0) def test_webob_error(self): req = Request.blank('/') req.content_length = '-1' resp = req.get_response(self.proxy) self.assertEqual(resp.status_int, 500, resp) def test_not_allowed_method(self): resp = self.app.options('/', status='*') self.assertEqual(resp.status_int, 405) def test_status(self): resp = self.app.get('/?status=404', status='*') self.assertEqual(resp.status_int, 404) def test_redirect(self): location = self.application_url + '/form.html' resp = self.app.get( '/?status=301%20Redirect&header-location=' + location, status='*') self.assertEqual(resp.status_int, 301, resp) self.assertEqual(resp.location, location) location = 'http://foo.com' resp = self.app.get( '/?status=301%20Redirect&header-location=' + location, status='*') self.assertEqual(resp.status_int, 301, resp) self.assertEqual(resp.location, location) location = '/foo' resp = self.app.get( '/?status=301%20Redirect&header-location=' + location, status='*') self.assertEqual(resp.status_int, 301, resp) self.assertEqual(resp.location, self.application_url + location) location = self.application_url + '/script_name/form.html' self.proxy.strip_script_name = False resp = self.app.get( '/?status=301%20Redirect&header-Location=' + location, status='*', extra_environ={'SCRIPT_NAME': '/script_name'}) self.assertEqual(resp.status_int, 301, resp) self.assertEqual(resp.location, location) def test_chunked(self): resp = self.app.get('/', headers=[('Transfer-Encoding', 'chunked')]) resp.mustcontain(no='chunked') def tearDown(self): self.server.shutdown()
class TestCorkscrew(unittest.TestCase): def setUp(self): database.initialize(SqliteDatabase(":memory:")) insertFixtures() app = CorkscrewApplication(PHF) app.register(Comment, endpoint="/comments") app.register( Person, related={"articles": Article}, endpoint="/people" ) app.register( Photo, related={"tags": Link(Tag, via=PhotoTag)}, endpoint="/photos" ) app.register( Article, related={ "comments": Comment, "revisions": Link(Revision, on="parent") }, endpoint="/articles" ) self.app = TestApp(app) def tearDown(self): database.close() def testList(self): result = self.app.get("/articles") self.assertEqual(result.status, "200 OK") JsonAPIValidator.validate_content_type(result.content_type) self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) self.assertIn("data", result.json) self.assertIs(len(result.json["data"]), len(ARTICLE_TITLES)) for row in result.json["data"]: self.assertEqual(row["type"], "article") self.assertIn("attributes", row) # expected attributes: title, created self.assertIs(len(row["attributes"]), 2) self.assertIn("title", row["attributes"]) self.assertIn("created", row["attributes"]) self.assertNotIn("author", row["attributes"]) self.assertIn(row["attributes"]["title"], ARTICLE_TITLES) self.assertIn("relationships", row) for key, relationship in row["relationships"].iteritems(): self.assertIn( key, ["comments", "cover", "author", "revisions"] ) self.assertIn("links", relationship) self.assertIn("related", relationship["links"]) self.assertIn("self", relationship["links"]) def testGet(self): result = self.app.get("/articles/1") self.assertEqual(result.status, "200 OK") self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) self.assertIn("data", result.json) # we want a single result self.assertEqual(type(result.json["data"]), type({})) self.assertIn("attributes", result.json["data"]) attributes = result.json["data"]["attributes"] self.assertEqual(attributes["title"], ARTICLE_TITLES[0]) self.assertIn("relationships", result.json["data"]) for key, rel in result.json["data"]["relationships"].iteritems(): self.assertIn(key, ["comments", "cover", "author", "revisions"]) self.assertIn("links", rel) self.assertIn("related", rel["links"]) self.assertIn("self", rel["links"]) self.assertIsInstance( result.json["data"]["relationships"]["comments"]["data"], list ) def testPost(self): request = { u"data": { u"type": u"article", u"attributes": { u"title": u"Test entry" }, u"relationships": { u"author": { u"data": {u"id": u"1", u"type": u"person"} } } } } JsonAPIValidator.validate_jsonapi(request, True) result = self.app.post_json("/articles", params=request) self.assertEqual(result.status, "200 OK") JsonAPIValidator.validate_content_type(result.content_type) self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) self.assertIn("relationships", result.json["data"]) for key, rel in result.json["data"]["relationships"].iteritems(): self.assertIn(key, ["comments", "cover", "author", "revisions"]) self.assertIn("links", rel) self.assertIn("related", rel["links"]) self.assertIn("self", rel["links"]) def testPatch(self): request = { u"data": { u"type": u"article", u"id": u"1", u"attributes": { u"title": u"Changed First Entry" } } } JsonAPIValidator.validate_jsonapi(request) result = self.app.patch_json("/articles/1", params=request) self.assertIn( result.status, ["202 Accepted", "200 OK", "204 No Content"] ) if result.status == "204 No Content": # nothing more to test return JsonAPIValidator.validate_content_type(result.content_type) self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) self.assertEqual( result.json["data"]["attributes"]["title"], "Changed First Entry" ) def testDelete(self): result = self.app.delete("/articles/1") self.assertIn( result.status, ["202 Accepted", "204 No Content", "200 OK"] ) def testGetRelated(self): result = self.app.get("/articles/1") self.assertEqual(result.status, "200 OK") JsonAPIValidator.validate_content_type(result.content_type) self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) result = self.app.get( result.json["data"]["relationships"]["author"]["links"]["related"] ) self.assertEqual(result.status, "200 OK") JsonAPIValidator.validate_content_type(result.content_type) self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) self.assertIn("data", result.json) self.assertEqual(type(result.json["data"]), type({})) self.assertEqual(result.json["data"]["type"], "person") self.assertEqual(result.json["data"]["id"], "1") def testGetRelationship(self): result = self.app.get("/articles/1") self.assertEqual(result.status, "200 OK") JsonAPIValidator.validate_content_type(result.content_type) self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) result = self.app.get( result.json["data"]["relationships"]["author"]["links"]["self"] ) self.assertEqual(result.status, "200 OK") JsonAPIValidator.validate_content_type(result.content_type) self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) self.assertIn("data", result.json) self.assertIsInstance(result.json["data"], dict) self.assertEqual(result.json["data"]["type"], "person") self.assertEqual(result.json["data"]["id"], "1") def testPatchRelationship(self): result = self.app.get("/articles/1") self.assertEqual(result.status, "200 OK") JsonAPIValidator.validate_content_type(result.content_type) self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) rel = result.json["data"]["relationships"]["author"]["links"]["self"] request = { u"data": {u"type": u"person", u"id": u"2"} } JsonAPIValidator.validate_jsonapi(request) result = self.app.patch_json(rel, params=request) self.assertIn( result.status, ["200 OK", "202 Accepted", "204 No Content"] ) if result.status == "204 No Content": self.assertIs(len(result.body), 0) elif result.status == "200 OK": self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) def testFetchingDataCollection(self): result = self.app.get("/articles") JsonAPIValidator.validate_content_type(result.content_type) self.assertEqual(result.status, "200 OK") JsonAPIValidator.validate_jsonapi(result.json) self.assertIs(len(result.json["data"]), 2) for entry in result.json["data"]: self.assertEqual(entry["type"], "article") self.assertIsInstance(entry["id"], unicode) self.assertIn(entry["attributes"]["title"], ARTICLE_TITLES) Article.delete().where(True).execute() result = self.app.get("/articles") JsonAPIValidator.validate_content_type(result.content_type) self.assertEqual(result.status, "200 OK") self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) self.assertIs(len(result.json["data"]), 0) def testFetchingNullRelationship(self): result = self.app.get("/articles/1") rel = result.json["data"]["relationships"]["cover"]["links"]["related"] result = self.app.get(rel) JsonAPIValidator.validate_content_type(result.content_type) self.assertEqual(result.status, "200 OK") self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) self.assertIsNone(result.json["data"]) def testFetchingMissingSingleResource(self): result = self.app.get("/article/1337", status=404) self.assertIsNotNone(result.json) JsonAPIValidator.validate_jsonapi(result.json) def testCreatingResourceWithReferences(self): request = { u"data": { u"type": u"photo", u"attributes": { u"title": u"Ember Hamster", u"src": u"http://example.com/images/productivity.png" }, u"relationships": { u"photographer": { u"data": {u"type": u"people", u"id": u"2"} } } } } JsonAPIValidator.validate_jsonapi(request, True) result = self.app.post_json("/photos", params=request) JsonAPIValidator.validate_content_type(result.content_type) JsonAPIValidator.validate_jsonapi(result.json) if not result.location: warnings.warn( "The response SHOULD include a Location header identifying the" "location of the newly created resource." ) else: res = self.app.get(result.location) self.assertIsNotNone(res.json) JsonAPIValidator.validate_jsonapi(res.json) def testCreatingResourceWithMissingRequiredAttributeShouldFail(self): request = { u"data": { u"type": u"person", u"attributes": { u"name": u"Eve Bobbington" # attribute 'age' is missing } } } JsonAPIValidator.validate_jsonapi(request, True) result = self.app.post_json("/people", params=request, status=400) JsonAPIValidator.validate_content_type(result.content_type) JsonAPIValidator.validate_jsonapi(result.json) def testCreateResourceWithAlreadyExistingId(self): request = { u"data": { u"type": u"person", u"id": u"1", u"attributes": { u"name": "Jimmy Cricket", u"age": 12 } } } JsonAPIValidator.validate_jsonapi(request) # expect this to fail result = self.app.post_json("/people", params=request, status=409) JsonAPIValidator.validate_jsonapi(result.json) def testUpdatingResourceViaSelfLink(self): UPDATE_TITLE = u"Five Ways You Have Never Tried To Access Your Data" result = self.app.get("/articles/1") update_uri = result.json["data"]["links"]["self"] request = { u"data": { u"type": u"article", u"id": u"1", u"attributes": { u"title": UPDATE_TITLE } } } JsonAPIValidator.validate_jsonapi(request) res = self.app.patch_json(update_uri, params=request) if "204" not in res.status: JsonAPIValidator.validate_content_type(res.content_type) res = self.app.get("/articles/1") self.assertEqual(res.json["data"]["attributes"]["title"], UPDATE_TITLE) def testUpdatingResourceRelationships(self): result = self.app.get("/articles/1") request = result.json # Person(1) is the current author self.assertEqual( result.json["data"]["relationships"]["author"]["data"]["id"], "1" ) # don't update attributes, server must ignore missing attributes del request["data"]["attributes"] # do not update the 'comments' and 'cover' relationships del request["data"]["relationships"]["comments"] del request["data"]["relationships"]["cover"] del request["data"]["relationships"]["revisions"] ptype = request["data"]["relationships"]["author"]["data"]["type"] # change author to Person(2) request["data"]["relationships"]["author"] = { u"data": { u"id": u"2", u"type": ptype } } JsonAPIValidator.validate_jsonapi(request) result = self.app.patch_json("/articles/1", params=request) result = self.app.get("/articles/1") self.assertNotEqual( result.json["data"]["relationships"]["author"]["data"]["id"], "1" ) self.assertEqual( result.json["data"]["relationships"]["author"]["data"]["id"], "2" ) self.assertEqual( result.json["data"]["attributes"]["title"], ARTICLE_TITLES[0] ) def testDeletingIndividualResource(self): result = self.app.get("/photos/1") JsonAPIValidator.validate_jsonapi(result.json) result = self.app.delete("/photos/1") if result.status_int not in [202, 204, 200]: warnings.warn("Delete: A server MAY respond with other HTTP status" "codes. This code is unknown to the specification.") if result.status_int == 200: JsonAPIValidator.validate_jsonapi(result.json) # the resource should be gone now self.app.get("/photos/1", status=404) def testFetchingRelatedOneToNResource(self): result = self.app.get("/articles/1/comments") JsonAPIValidator.validate_jsonapi(result.json) for entry in result.json["data"]: self.assertIn(entry["attributes"]["body"], COMMENT_BODIES) self.assertEqual( entry["relationships"]["author"]["data"]["id"], "2" ) self.assertEqual( entry["relationships"]["article"]["data"]["id"], "1" ) def testListingRelatedOneToNResource(self): result = self.app.get("/articles/1/relationships/comments") JsonAPIValidator.validate_jsonapi(result.json) for entry in result.json["data"]: JsonAPIValidator.validate_resource_identifier(entry) def testPatchingRelatedOneToNResourceShouldFail(self): result = self.app.get("/people/1/articles") self.assertIs(len(result.json["data"]), 2) for entry in result.json["data"]: self.assertIn(entry["attributes"]["title"], ARTICLE_TITLES) # it is not allowed to orphan an article request = { u"data": { u"id": u"1", u"type": u"person", u"relationships": { u"articles": { u"data": [] } } } } JsonAPIValidator.validate_jsonapi(request) result = self.app.patch_json("/people/1", params=request, status=400) JsonAPIValidator.validate_jsonapi(result.json) self.assertIn("orphan", result.json["errors"][0]["title"]) def testPatchingRelatedOneToNResourceShouldSucceed(self): result = self.app.get("/articles/2/cover") self.assertIsInstance(result.json["data"], dict) request = { u"data": { u"id": u"2", u"type": u"article", u"relationships": { u"cover": { u"data": None } } } } JsonAPIValidator.validate_jsonapi(request) result = self.app.patch_json("/articles/2", params=request) result = self.app.get("/articles/2/cover") self.assertIsNone(result.json["data"]) def testPatchingRelatedOneToMResource(self): result = self.app.get("/articles/1/relationships/comments") self.assertIsInstance(result.json["data"], list) del result.json["data"][0] request = { u"data": result.json["data"] } JsonAPIValidator.validate(request) result = self.app.patch_json( "/articles/1/relationships/comments", params=request ) def testPatchingRelatedNToMResource(self): result = self.app.get("/photos/1/relationships/tags") self.assertIsInstance(result.json["data"], list) self.assertIs(len(result.json["data"]), 2) request = { u"data": result.json["data"] } # remove one tag request["data"].pop() self.app.patch_json("/photos/1/relationships/tags", params=request) result = self.app.get("/photos/1/relationships/tags") self.assertIsInstance(result.json["data"], list) self.assertIs(len(result.json["data"]), 1) def testGetNToMRelationship(self): result = self.app.get("/photos/1/tags") JsonAPIValidator.validate(result.json) self.assertIs(len(result.json["data"]), 2) for tag in result.json["data"]: self.assertIn(tag["attributes"]["name"], TAG_NAMES) def testValidateForwardRelationship(self): result = self.app.get("/photos") # Photo has a forward relationship to Person (photographer) for entry in result.json["data"]: self.assertIn("relationships", entry) for name, relationship in entry["relationships"].iteritems(): self.assertIn(name, ["photographer", "tags"]) relationship = entry["relationships"]["photographer"] data = relationship["data"] # retrieve the /relationships link subresult = self.app.get(relationship["links"]["self"]) self.assertEqual(subresult.json["data"], data) # retrieve the related object subresult = self.app.get(relationship["links"]["related"]) # type and id must match self.assertEqual(subresult.json["data"]["id"], data["id"]) self.assertEqual(subresult.json["data"]["type"], data["type"]) # retrieve the related self link and test if it's the same object subsubresult = self.app.get( subresult.json["data"]["links"]["self"] ) self.assertEqual(subresult.json["data"], subsubresult.json["data"]) def testValidateReverseRelationships(self): result = self.app.get("/photos") # Photo has a reverse relationship to Tag (tags, via PhotoTag) for entry in result.json["data"]: self.assertIn("relationships", entry) for name, relationship in entry["relationships"].iteritems(): self.assertIn(name, ["photographer", "tags"]) relationship = entry["relationships"]["tags"] data = relationship["data"] # retrieve the /relationships link subresult = self.app.get(relationship["links"]["self"]) self.assertEqual(subresult.json["data"], data) # retrieve the related objects subresult = self.app.get(relationship["links"]["related"]) # type and id must match for subentry in subresult.json["data"]: self.assertIn( {"id": subentry["id"], "type": subentry["type"]}, data ) if "links" in subentry and "self" in subentry["links"]: subsubresult = self.app.get(subentry["links"]["self"]) self.assertEqual(subentry, subsubresult.json["data"]) def testIncludeParameterForwardRelationship(self): result = self.app.get("/articles/2?include=cover") JsonAPIValidator.validate(result.json) self.assertIn("included", result.json) # the server must not return any other fields than requested self.assertIs(len(result.json["included"]), 1) ref = result.json["data"]["relationships"]["cover"]["data"] inc = result.json["included"][0] self.assertEqual(inc["type"], ref["type"]) self.assertEqual(inc["id"], ref["id"]) # the self link must be valid and refer to the same object subresult = self.app.get(inc["links"]["self"]) self.assertEqual(subresult.json["data"], inc) def testIncludeParameterReverseRelationship(self): result = self.app.get("/articles/1?include=comments") JsonAPIValidator.validate(result.json) self.assertIn("included", result.json) # the server must not return any other fields than requested self.assertIs(len(result.json["included"]), len(COMMENT_BODIES)) refs = result.json["data"]["relationships"]["comments"]["data"] for inc in result.json["included"]: self.assertIn({"id": inc["id"], "type": inc["type"]}, refs) # the self link must be valid and refer to the same object subresult = self.app.get(inc["links"]["self"]) self.assertEqual(subresult.json["data"], inc) self.assertEqual( subresult.json["links"]["self"], inc["links"]["self"] ) def testIncludeParameterWithInvalidFields(self): self.app.get("/articles/1?include=invalid-field", status=400) self.app.get("/articles/1?include=author,invalid-field", status=400) def testIncludeParameterWithCircularRelationships(self): self.app.get("/articles/1?include=comments.articles", status=400) self.app.get( "/articles/1?include=comments.articles.comments", status=400 ) def testSparseFieldsets(self): result = self.app.get("/people/1?fields[person]=age") JsonAPIValidator.validate(result.json) self.assertNotIn("name", result.json["data"]["attributes"]) self.assertIn("age", result.json["data"]["attributes"]) def testSparseFieldsetsWithIncludedObjects(self): result = self.app.get("/articles/1?include=comments&fields[comment]=") JsonAPIValidator.validate(result.json) for inc in result.json["included"]: self.assertNotIn("attributes", inc) result = self.app.get( "/comments/1?include=article.author&fields[person]=age" ) JsonAPIValidator.validate(result.json) is_included = False for inc in result.json["included"]: if inc["type"] == "person": is_included = True self.assertIn("age", inc["attributes"]) self.assertNotIn("name", inc["attributes"]) self.assertIsNotNone(is_included) def testLinkWithSpecifiedField(self): result = self.app.get("/articles/1/relationships/revisions") JsonAPIValidator.validate(result.json) def testOPTIONSRequest(self): # all of these should return all 200 OK code self.app.options("/articles") self.app.options("/articles/1") self.app.options("/articles/1/cover") self.app.options("/articles/1/relationships/cover") self.app.options("/photos/2/tags") self.app.options("/articles/1?include=author") def testCORSHeaders(self): # simulate a browser result = self.app.options("/articles", headers={ "Origin": "http://example.org", "Access-Control-Request-Method": "GET", "Access-Control-Request-Headers": "X-Requested-With" }) self.assertIn("Access-Control-Allow-Origin", result.headers) self.assertIn("Access-Control-Allow-Methods", result.headers) self.assertEqual(result.headers["Access-Control-Allow-Origin"], "*") methods = map( lambda x: x.strip(), result.headers["Access-Control-Allow-Methods"].split(",") ) self.assertIn("GET", methods) self.assertIn( "X-Requested-With".lower(), result.headers["Access-Control-Allow-Headers"].lower() )
class AppUser: """:class:`webtest.TestApp` wrapper for backend functional testing.""" def __init__(self, app, rest_url: str='http://localhost', base_path: str='/', header: dict=None): """Initialize self.""" self.app = TestApp(app) """:class:`webtest.TestApp`to send requests to the backend server.""" self.rest_url = rest_url """backend server url to generate request urls.""" self.base_path = base_path """path prefix to generate request urls.""" self.header = header or {} """default header for requests, mostly for authentication.""" self._resolver = DottedNameResolver() def post_resource(self, path: str, iresource: IInterface, cstruct: dict) -> TestResponse: """Build and post request to create a new resource.""" url = self._build_url(path) props = self._build_post_body(iresource, cstruct) resp = self.app.post_json(url, props, headers=self.header, expect_errors=True) return resp def put(self, path: str, cstruct: dict={}) -> TestResponse: """Put request to modify a resource.""" url = self._build_url(path) resp = self.app.put_json(url, cstruct, headers=self.header, expect_errors=True) return resp def post(self, path: str, cstruct: dict={}) -> TestResponse: """Post request to create a new resource.""" url = self._build_url(path) resp = self.app.post_json(url, cstruct, headers=self.header, expect_errors=True) return resp def _build_post_body(self, iresource: IInterface, cstruct: dict) -> dict: return {'content_type': iresource.__identifier__, 'data': cstruct} def _build_url(self, path: str) -> str: if path.startswith('http'): return path return self.rest_url + self.base_path + path def batch(self, subrequests: list): """Build and post batch request to the backend rest server.""" resp = self.app.post_json(batch_url, subrequests, headers=self.header, expect_errors=True) return resp def get(self, path: str, params={}) -> TestResponse: """Send get request to the backend rest server.""" url = self._build_url(path) resp = self.app.get(url, headers=self.header, params=params, expect_errors=True) return resp def options(self, path: str) -> TestResponse: """Send options request to the backend rest server.""" url = self._build_url(path) resp = self.app.options(url, headers=self.header, expect_errors=True) return resp def get_postable_types(self, path: str) -> []: """Send options request and return the postable content types.""" resp = self.options(path) if 'POST' not in resp.json: return [] post_request_body = resp.json['POST']['request_body'] type_names = sorted([r['content_type'] for r in post_request_body]) iresources = [self._resolver.resolve(t) for t in type_names] return iresources
class TestHttplib(unittest.TestCase): client = "httplib" client_options = {} def setUp(self): self.server = StopableWSGIServer.create(debug_app) self.application_url = self.server.application_url.rstrip("/") self.proxy = proxies.HostProxy(self.application_url, client=self.client, **self.client_options) self.app = TestApp(self.proxy) def test_form(self): resp = self.app.get("/form.html") resp.mustcontain("</form>") form = resp.form form["name"] = "gawel" resp = form.submit() resp.mustcontain("name=gawel") def test_head(self): resp = self.app.head("/form.html") self.assertEqual(resp.status_int, 200) self.assertEqual(len(resp.body), 0) def test_webob_error(self): req = Request.blank("/") req.content_length = "-1" resp = req.get_response(self.proxy) self.assertEqual(resp.status_int, 500, resp) def test_not_allowed_method(self): resp = self.app.options("/", status="*") self.assertEqual(resp.status_int, 405) def test_status(self): resp = self.app.get("/?status=404", status="*") self.assertEqual(resp.status_int, 404) def test_redirect(self): location = self.application_url + "/form.html" resp = self.app.get("/?status=301%20Redirect&header-location=" + location, status="*") self.assertEqual(resp.status_int, 301, resp) self.assertEqual(resp.location, location) location = "http://foo.com" resp = self.app.get("/?status=301%20Redirect&header-location=" + location, status="*") self.assertEqual(resp.status_int, 301, resp) self.assertEqual(resp.location, location) location = "/foo" resp = self.app.get("/?status=301%20Redirect&header-location=" + location, status="*") self.assertEqual(resp.status_int, 301, resp) self.assertEqual(resp.location, self.application_url + location) location = self.application_url + "/script_name/form.html" self.proxy.strip_script_name = False resp = self.app.get( "/?status=301%20Redirect&header-Location=" + location, status="*", extra_environ={"SCRIPT_NAME": "/script_name"}, ) self.assertEqual(resp.status_int, 301, resp) self.assertEqual(resp.location, location) def test_chunked(self): resp = self.app.get("/", headers=[("Transfer-Encoding", "chunked")]) resp.mustcontain(no="chunked") def test_quoted_utf8_url(self): path = "/targets/NR2F1%C3%82-human/" resp = self.app.get(path) resp.mustcontain(b"PATH_INFO: /targets/NR2F1\xc3\x82-human/") def tearDown(self): self.server.shutdown()
class AppUser: """:class:`webtest.TestApp` wrapper for backend functional testing.""" def __init__( self, app_router: Router, base_path: str = '/', header: dict = None, user_path: str = '', user_login: str = '', user_password: str = '', ): """Initialize self.""" self.app_router = app_router """The adhocracy wsgi application""" self.app = TestApp(app_router) """:class:`webtest.TestApp`to send requests to the backend server.""" self.rest_url = rest_url() """backend server url to generate request urls.""" self.base_path = base_path """path prefix to generate request urls.""" self.header = header or {} """default header for requests, mostly for authentication. If not set, `user_login` and `user_password` is used to login, the new authentication header is stored in `header`. """ if user_password and user_login and not header: token, user_path = self._get_token_and_user_path( user_login, user_password) self.header = {UserTokenHeader: token} self.user_password = user_password """password for authenticated user.""" self.user_login = user_login """login name for authenticated user.""" self.user_path = user_path """path for authenticated user.""" self._resolver = DottedNameResolver() def _get_token_and_user_path(self, login: str, password: str) -> tuple: login_page = self.rest_url + '/login_username' data = {'name': login, 'password': password} resp = self.app.post_json(login_page, data).json return resp['user_token'], resp['user_path'] def post_resource(self, path: str, iresource: IInterface, cstruct: dict) -> TestResponse: """Build and post request to create a new resource.""" url = self._build_url(path) props = self._build_post_body(iresource, cstruct) resp = self.app.post_json(url, props, headers=self.header, expect_errors=True) return resp def put( self, path: str, cstruct: dict = {}, upload_files: [(str, str, bytes)] = None, extra_headers: dict = {}, ) -> TestResponse: """Put request to modify a resource.""" url = self._build_url(path) headers = copy(self.header) headers.update(extra_headers) kwargs = { 'headers': headers, 'expect_errors': True, } if upload_files: kwargs['upload_files'] = upload_files resp = self.app.put(url, cstruct, **kwargs) else: resp = self.app.put_json(url, cstruct, **kwargs) return resp def post( self, path: str, cstruct: dict = {}, upload_files: [(str, str, bytes)] = None, extra_headers: dict = {}, ) -> TestResponse: """Post request to create a new resource.""" url = self._build_url(path) headers = copy(self.header) headers.update(extra_headers) kwargs = { 'headers': headers, 'expect_errors': True, } if upload_files: kwargs['upload_files'] = upload_files resp = self.app.post(url, cstruct, **kwargs) else: resp = self.app.post_json(url, cstruct, **kwargs) return resp def _build_post_body(self, iresource: IInterface, cstruct: dict) -> dict: return {'content_type': iresource.__identifier__, 'data': cstruct} def _build_url(self, path: str) -> str: if path.startswith('http'): return path elif path.startswith('/') and (self.base_path == '\\'): return self.rest_url + path else: return self.rest_url + self.base_path + path def batch(self, subrequests: list): """Build and post batch request to the backend rest server.""" resp = self.app.post_json(batch_url, subrequests, headers=self.header, expect_errors=True) return resp def head(self, path: str, extra_headers={}) -> TestResponse: """Send head request to the backend rest server.""" url = self._build_url(path) headers = copy(self.header) headers.update(extra_headers) resp = self.app.head(url, headers=headers, expect_errors=True) return resp def get(self, path: str, params={}, extra_headers={}) -> TestResponse: """Send get request to the backend rest server.""" url = self._build_url(path) headers = copy(self.header) headers.update(extra_headers) resp = self.app.get(url, headers=headers, params=params, expect_errors=True) return resp def delete(self, path: str) -> TestResponse: """Send delete request to the backend rest server.""" url = self._build_url(path) resp = self.app.delete(url, headers=self.header, expect_errors=True) return resp def options(self, path: str) -> TestResponse: """Send options request to the backend rest server.""" url = self._build_url(path) resp = self.app.options(url, headers=self.header, expect_errors=True) return resp def get_postable_types(self, path: str) -> []: """Send options request and return the postable content types.""" resp = self.options(path) if 'POST' not in resp.json: return [] post_request_body = resp.json['POST']['request_body'] type_names = sorted([r['content_type'] for r in post_request_body]) iresources = [self._resolver.resolve(t) for t in type_names] return iresources
class TestUserAPI(unittest.TestCase): """Tests API functions associated with actions a regular user can take. Note that all tests are in-process, we don't actually start a HTTP server. All administrative requirements will be set up with direct calls to eos_db.server, and all user calls will be done via self.app. """ def setUp(self): """Launch app using webtest with test settings""" self.appconf = get_app(test_ini) self.app = TestApp(self.appconf) #All auth via BasicAuth - never return the session cookie. self.app.cookiejar.set_policy(DefaultCookiePolicy(allowed_domains=[])) # This sets global var "engine" - in the case of SQLite this is a fresh RAM # DB each time. If we only did this on class instantiation the database would # be dirty and one test could influence another. # TODO - add a test that tests this. server.choose_engine("SQLite") # Punch in new user account with direct server call # This will implicitly generate the tables. user_id = self.create_user("testuser") #Here is what the user should look like when inspected self.user_json = { "name" : "testuser testuser", "handle" : "*****@*****.**", "id" : 1, "credits" : 0, "username": "******"} #print("user_id is %s" % str(user_id)) #print("user_from_db_is %s" % server.get_user_id_from_name("testuser")) server.touch_to_add_password(user_id, "asdf") # And log in as this user for all tests (via BasicAuth) # FIXME - switch to token auth to speed up the tests. self.app.authorization = ('Basic', ('testuser', 'asdf')) """Unauthenticated API functions. Should respond the same regardless of authentication. """ def test_home_view(self): """ Home view should respond with 200 OK. """ response = self.app.get('/', status=200) # Not sure why Ben implemented options, but it should still work. def test_options(self): """ Options should respond with 200 OK. """ response = self.app.options('/', status=200) """User API functions. The user functions in the API are primarily used by system utilities. Creating a user and password, and validating against the database in order to receive an access token, are prerequisites for using functions in later sections of the API. These can only be called by an administrator.""" def test_whoami(self): """ How do I find out who I am? """ response = self.app.get('/user') #We expect to be user 1, as the database is fresh. #All other items should be as per create_user("testuser") self.assertEqual( response.json, self.user_json ) def test_retrieve_my_info(self): """ Retrieving my own user info by name should give the same result as above.""" response = self.app.get('/users/testuser', status=200) #We expect to be user 1, as the database is fresh. #All other items should be as per create_user("testuser") self.assertEqual( response.json, self.user_json ) def test_retrieve_other_user_info(self): """ Retrieving info for another user should respond 200 OK. """ self.create_user("anotheruser") self.assertEqual(self.app.get('/users/anotheruser').json['name'], "anotheruser anotheruser") def test_retrieve_users(self): """ Add another couple of users. Three records should be returned, as there is already a testuser. """ self.create_user("foo") self.create_user("bar") response = self.app.get('/users') self.assertEqual(len(response.json), 3) #Unimplemented just now. @unittest.expectedFailure def test_delete_user(self): """ Delete a user. Should fail because the account does not have permission, but it actually fails because deletion is unimplemented. """ self.create_user("anotheruser") response = self.app.delete('/users/anotheruser', status=404) def test_change_my_password(self): """ Apply a password to our user. Check that we receive a 200 OK. Check we can log in with the new password but not the old. """ response = self.app.put('/user/password', {'password': '******'}) #This should work self.app.authorization = ('Basic', ('testuser', 'newpass')) self.app.get('/users/testuser') #This should fail as the password is now wrong. self.app.authorization = ('Basic', ('testuser', 'asdf')) self.app.get('/users/testuser', status=401) def test_change_other_password(self): """ Try to change password for another user, which should fail. """ self.create_user("anotheruser") response = self.app.put('/users/anotheruser/password', {'password': '******'}, status=401) def test_retrieve_user_credit(self): """ If administrator adds credit, I should be able to see it. See full credit tests in test_credit.py """ self.add_credit(123, 'testuser') #And retrieve it back response = self.app.get('/user') user_json = self.user_json.copy() user_json['credits'] = 123 self.assertEqual( response.json, user_json ) def test_retrieve_servers(self): """ A user can request a list of servers that they own. """ server_id = self.create_server('fooserver', 'testuser') my_servers = self.app.get('/servers').json self.assertTrue(server_id) self.assertEqual(len(my_servers), 1) self.assertEqual(my_servers[0]['artifact_name'], 'fooserver') def test_retrieve_user_touches(self): """ Retrieve a list of touches that the user has made to the database. This can only be requested by the user themselves, an agent or an administrator. """ def test_create_server(self): """ A regular user cannot create a server or give themselves ownership of a server, so this should produce an appropriate error. """ def test_create_server_owner(self): """ Add an owner to a server. Ensure that a 200 OK response results. """ #FIXME - move this to administrator tests. """ Server State-Change Functions. """ def test_start_server(self): """ Check that a server appears in state 'Starting' after using the relevant API call. This also tests the function 'retrieve_servers_in_state'. """ server_id = self.create_server('fooserver', 'testuser') self.app.post('/servers/fooserver/Starting') #1 - server should appear to be Starting in list of my servers. my_servers = self.app.get('/servers').json self.assertEqual(len(my_servers), 1) self.assertEqual(my_servers[0]['state'], 'Starting') #2 - server should appear to be Starting if I look at it directly my_server = self.app.get('/servers/fooserver').json self.assertEqual(my_server['state'], 'Starting') #3 - server should appear as the only server in state Starting servers_in_state = self.app.get('/states/Starting').json self.assertEqual(len(servers_in_state), 1) self.assertEqual(servers_in_state[0]['artifact_name'], 'fooserver') def test_error_by_id_server(self): """ Check that i can put a server into Error state, refereced by ID """ server_id = self.create_server('fooserver', 'testuser') self.app.post('/servers/by_id/%i/Error' % server_id) server_info = self.app.get('/servers/fooserver').json self.assertEqual(server_info['state'], 'Error') def test_boost_deboost_server(self): """ Test the boost call, which is done by putting the server into /Preparing. After this the server should be in the Preparing state and the user should have fewer credits. Also test the deboost. """ server_id = self.create_server('boostme', 'testuser') self.add_credit(123, 'testuser') self.app.post('/servers/boostme/Preparing', params=dict(hours=20, cores=2, ram=40)) #Check the user user_info = self.app.get('/user').json self.assertEqual(user_info['credits'], 103) #Check the server info_expected = dict(boosted="Boosted", boostremaining="19 hrs, 59 min", ram="40 GB", cores="2") server_info = self.app.get('/servers/boostme').json #Remove items I don't want to compare from server_info info_got = {k:str(server_info[k]) for k in server_info if k in info_expected} self.assertEqual(info_got, info_expected) #Deboost and check again self.app.post('/servers/boostme/Pre_Deboosting') #Check the user - we should be down 1 credit. user_info = self.app.get('/user').json self.assertEqual(user_info['credits'], 122) #Check the server once more. info_expected = dict(boosted="Unboosted", boostremaining="N/A", ram="16 GB", cores="1") server_info = self.app.get('/servers/boostme').json #Remove items I don't want to compare from server_info info_got = {k:str(server_info[k]) for k in server_info if k in info_expected} self.assertEqual(info_got, info_expected) def test_restart_server(self): """ Check that a server appears in state 'Restarted' after using the relevant API call. This also tests the function 'retrieve_servers_in_state'. """ def test_stop_server(self): """ Check that a server appears in state 'Stopped' after using the relevant API call. This also tests the function 'retrieve_servers_in_state'. """ def test_prepare_server(self): """ Check that a server appears in state 'Prepared' after using the relevant API call. This also tests the function 'retrieve_servers_in_state'. """ def test_pre_deboost_server(self): """ Check that a server appears in relevant state after using the relevant API call. This also tests the function 'retrieve_servers_in_state'. """ def test_stopped_server(self): """ Check that a server appears in relevant state after using the relevant API call. This also tests the function 'retrieve_servers_in_state'. """ def test_started_server(self): """ Check that a server appears in relevant state after using the relevant API call. This also tests the function 'retrieve_servers_in_state'. """ def test_prepared_server(self): """ Check that a server appears in relevant state after using the relevant API call. This also tests the function 'retrieve_servers_in_state'. """ def test_predeboosted_server(self): """ Check that a server appears in relevant state after using the relevant API call. This also tests the function 'retrieve_servers_in_state'. """ def test_retrieve_server(self): """ Pull back details of our server by name. """ def test_retrieve_server_by_id(self): """ Our server will have ID 1. Check that we can retrieve details of it.""" def test_update_server(self): """ Not currently implemented. """ def test_delete_server(self): """ Not currently implemented. """ def test_set_server_specification(self): """ Follows hard-coded rules for machine behaviour. Set machine CPUs to 2. Check, should pass. Set machine CPUs to 65000. Check, should fail. Set machine RAM to 16. Check, should pass. Set machine RAM to 65000. Check, should fail.""" def test_get_server_specification(self): """ Check that machine RAM and Cores are 2 and 16 as above. """ def test_retrieve_job_progress(self): """ Not currently implemented. """ def test_retrieve_server_touches(self): """ Not currently implemented. """ ############################################################################### # Support Functions, calling the server code directly # ############################################################################### def create_user(self, name): #Since we are not logged in as the administrator, do this directly return server.create_user("users", name + "@example.com", name + " " + name, name) # Servers should not normally have uuid set to name, but maybe for testing it doesn't # matter? def create_server(self, name, owner): owner_id = server.get_user_id_from_name(owner) server_id = server.create_appliance(name, name) server.touch_to_add_ownership(server_id, owner_id) return server_id def add_credit(self, amount, owner): owner_id = server.get_user_id_from_name(owner) server.touch_to_add_credit(owner_id, int(amount))
class TestCORS(TestCase): def setUp(self): self.config = testing.setUp() self.config.include("cornice") self.config.scan("cornice.tests.test_cors") self.app = TestApp(CatchErrors(self.config.make_wsgi_app())) def tearDown(self): testing.tearDown() def test_preflight_missing_headers(self): # we should have an OPTION method defined. # If we just try to reach it, without using correct headers: # "Access-Control-Request-Method"or without the "Origin" header, # we should get a 400. resp = self.app.options("/squirel", status=400) self.assertEquals(len(resp.json["errors"]), 2) def test_preflight_missing_origin(self): resp = self.app.options("/squirel", headers={"Access-Control-Request-Method": "GET"}, status=400) self.assertEquals(len(resp.json["errors"]), 1) def test_preflight_missing_request_method(self): resp = self.app.options("/squirel", headers={"Origin": "foobar.org"}, status=400) self.assertEquals(len(resp.json["errors"]), 1) def test_preflight_incorrect_origin(self): # we put "lolnet.org" where only "notmyidea.org" is authorized resp = self.app.options( "/squirel", headers={"Origin": "lolnet.org", "Access-Control-Request-Method": "GET"}, status=400 ) self.assertEquals(len(resp.json["errors"]), 1) def test_preflight_correct_origin(self): resp = self.app.options("/squirel", headers={"Origin": "notmyidea.org", "Access-Control-Request-Method": "GET"}) self.assertEquals(resp.headers["Access-Control-Allow-Origin"], "notmyidea.org") allowed_methods = resp.headers["Access-Control-Allow-Methods"].split(",") self.assertNotIn("POST", allowed_methods) self.assertIn("GET", allowed_methods) self.assertIn("PUT", allowed_methods) self.assertIn("HEAD", allowed_methods) allowed_headers = resp.headers["Access-Control-Allow-Headers"].split(",") self.assertIn("X-My-Header", allowed_headers) self.assertNotIn("X-Another-Header", allowed_headers) def test_preflight_deactivated_method(self): self.app.options( "/squirel", headers={"Origin": "notmyidea.org", "Access-Control-Request-Method": "POST"}, status=400 ) def test_preflight_origin_not_allowed_for_method(self): self.app.options( "/squirel", headers={"Origin": "notmyidea.org", "Access-Control-Request-Method": "PUT"}, status=400 ) def test_preflight_credentials_are_supported(self): resp = self.app.options("/spam", headers={"Origin": "notmyidea.org", "Access-Control-Request-Method": "GET"}) self.assertIn("Access-Control-Allow-Credentials", resp.headers) self.assertEquals(resp.headers["Access-Control-Allow-Credentials"], "true") def test_preflight_credentials_header_not_included_when_not_needed(self): resp = self.app.options("/spam", headers={"Origin": "notmyidea.org", "Access-Control-Request-Method": "POST"}) self.assertNotIn("Access-Control-Allow-Credentials", resp.headers) def test_preflight_contains_max_age(self): resp = self.app.options("/spam", headers={"Origin": "notmyidea.org", "Access-Control-Request-Method": "GET"}) self.assertIn("Access-Control-Max-Age", resp.headers) self.assertEquals(resp.headers["Access-Control-Max-Age"], "42") def test_resp_dont_include_allow_origin(self): resp = self.app.get("/squirel") # omit the Origin header self.assertNotIn("Access-Control-Allow-Origin", resp.headers) self.assertEquals(resp.json, "squirels") def test_responses_include_an_allow_origin_header(self): resp = self.app.get("/squirel", headers={"Origin": "notmyidea.org"}) self.assertIn("Access-Control-Allow-Origin", resp.headers) self.assertEquals(resp.headers["Access-Control-Allow-Origin"], "notmyidea.org") def test_credentials_are_included(self): resp = self.app.get("/spam", headers={"Origin": "notmyidea.org"}) self.assertIn("Access-Control-Allow-Credentials", resp.headers) self.assertEquals(resp.headers["Access-Control-Allow-Credentials"], "true") def test_headers_are_exposed(self): resp = self.app.get("/squirel", headers={"Origin": "notmyidea.org"}) self.assertIn("Access-Control-Expose-Headers", resp.headers) headers = resp.headers["Access-Control-Expose-Headers"].split(",") self.assertIn("X-My-Header", headers) def test_preflight_request_headers_are_included(self): resp = self.app.options( "/squirel", headers={ "Origin": "notmyidea.org", "Access-Control-Request-Method": "GET", "Access-Control-Request-Headers": "foo,bar,baz", }, ) # per default, they should be authorized, and returned in the list of # authorized headers headers = resp.headers["Access-Control-Allow-Headers"].split(",") self.assertIn("foo", headers) self.assertIn("bar", headers) self.assertIn("baz", headers) def test_preflight_request_headers_isnt_too_permissive(self): self.app.options( "/eggs", headers={ "Origin": "notmyidea.org", "Access-Control-Request-Method": "GET", "Access-Control-Request-Headers": "foo,bar,baz", }, status=400, ) def test_preflight_headers_arent_case_sensitive(self): self.app.options( "/spam", headers={ "Origin": "notmyidea.org", "Access-Control-Request-Method": "GET", "Access-Control-Request-Headers": "x-my-header", }, )
class AppUser: """:class:`webtest.TestApp` wrapper for backend functional testing.""" def __init__(self, app_router: Router, rest_url: str='http://localhost', base_path: str='/', header: dict=None, user_path: str='', user_login: str='', user_password: str='', ): """Initialize self.""" self.app_router = app_router """The adhocracy wsgi application""" self.app = TestApp(app_router) """:class:`webtest.TestApp`to send requests to the backend server.""" self.rest_url = rest_url """backend server url to generate request urls.""" self.base_path = base_path """path prefix to generate request urls.""" self.header = header or {} """default header for requests, mostly for authentication. If not set, `user_login` and `user_password` is used to login, the new authentication header is stored in `header`. """ if user_password and user_login and not header: token, user_path = self._get_token_and_user_path(user_login, user_password) self.header = {UserTokenHeader: token} self.user_password = user_password """password for authenticated user.""" self.user_login = user_login """login name for authenticated user.""" self.user_path = user_path """path for authenticated user.""" self._resolver = DottedNameResolver() def _get_token_and_user_path(self, login: str, password: str) -> tuple: login_page = self.rest_url + '/login_username' data = {'name': login, 'password': password} resp = self.app.post_json(login_page, data).json return resp['user_token'], resp['user_path'] def post_resource(self, path: str, iresource: IInterface, cstruct: dict) -> TestResponse: """Build and post request to create a new resource.""" url = self._build_url(path) props = self._build_post_body(iresource, cstruct) resp = self.app.post_json(url, props, headers=self.header, expect_errors=True) return resp def put(self, path: str, cstruct: dict={}, upload_files: [(str, str, bytes)]=None, ) -> TestResponse: """Put request to modify a resource.""" url = self._build_url(path) kwargs = {'headers': self.header, 'expect_errors': True, } if upload_files: kwargs['upload_files'] = upload_files resp = self.app.put(url, cstruct, **kwargs) else: resp = self.app.put_json(url, cstruct, **kwargs) return resp def post(self, path: str, cstruct: dict={}, upload_files: [(str, str, bytes)]=None, ) -> TestResponse: """Post request to create a new resource.""" url = self._build_url(path) kwargs = {'headers': self.header, 'expect_errors': True, } if upload_files: kwargs['upload_files'] = upload_files resp = self.app.post(url, cstruct, **kwargs) else: resp = self.app.post_json(url, cstruct, **kwargs) return resp def _build_post_body(self, iresource: IInterface, cstruct: dict) -> dict: return {'content_type': iresource.__identifier__, 'data': cstruct} def _build_url(self, path: str) -> str: if path.startswith('http'): return path return self.rest_url + self.base_path + path def batch(self, subrequests: list): """Build and post batch request to the backend rest server.""" resp = self.app.post_json(batch_url, subrequests, headers=self.header, expect_errors=True) return resp def get(self, path: str, params={}) -> TestResponse: """Send get request to the backend rest server.""" url = self._build_url(path) resp = self.app.get(url, headers=self.header, params=params, expect_errors=True) return resp def delete(self, path: str) -> TestResponse: """Send delete request to the backend rest server.""" url = self._build_url(path) resp = self.app.delete(url, headers=self.header, expect_errors=True) return resp def options(self, path: str) -> TestResponse: """Send options request to the backend rest server.""" url = self._build_url(path) resp = self.app.options(url, headers=self.header, expect_errors=True) return resp def get_postable_types(self, path: str) -> []: """Send options request and return the postable content types.""" resp = self.options(path) if 'POST' not in resp.json: return [] post_request_body = resp.json['POST']['request_body'] type_names = sorted([r['content_type'] for r in post_request_body]) iresources = [self._resolver.resolve(t) for t in type_names] return iresources
class TestCORS(TestCase): def setUp(self): self.config = testing.setUp() self.config.include("cornice") self.config.add_route('noservice', '/noservice') self.config.scan("cornice.tests.test_cors") self.app = TestApp(CatchErrors(self.config.make_wsgi_app())) def tearDown(self): testing.tearDown() def test_preflight_missing_headers(self): # we should have an OPTION method defined. # If we just try to reach it, without using correct headers: # "Access-Control-Request-Method"or without the "Origin" header, # we should get a 400. resp = self.app.options('/squirel', status=400) self.assertEquals(len(resp.json['errors']), 2) def test_preflight_missing_origin(self): resp = self.app.options( '/squirel', headers={'Access-Control-Request-Method': 'GET'}, status=400) self.assertEquals(len(resp.json['errors']), 1) def test_preflight_missing_request_method(self): resp = self.app.options('/squirel', headers={'Origin': 'foobar.org'}, status=400) self.assertEquals(len(resp.json['errors']), 1) def test_preflight_incorrect_origin(self): # we put "lolnet.org" where only "notmyidea.org" is authorized resp = self.app.options('/squirel', headers={ 'Origin': 'lolnet.org', 'Access-Control-Request-Method': 'GET' }, status=400) self.assertEquals(len(resp.json['errors']), 1) def test_preflight_correct_origin(self): resp = self.app.options('/squirel', headers={ 'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'GET' }) self.assertEquals(resp.headers['Access-Control-Allow-Origin'], 'notmyidea.org') allowed_methods = ( resp.headers['Access-Control-Allow-Methods'].split(',')) self.assertNotIn('POST', allowed_methods) self.assertIn('GET', allowed_methods) self.assertIn('PUT', allowed_methods) self.assertIn('HEAD', allowed_methods) allowed_headers = ( resp.headers['Access-Control-Allow-Headers'].split(',')) self.assertIn('X-My-Header', allowed_headers) self.assertNotIn('X-Another-Header', allowed_headers) def test_preflight_deactivated_method(self): self.app.options('/squirel', headers={ 'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'POST' }, status=400) def test_preflight_origin_not_allowed_for_method(self): self.app.options('/squirel', headers={ 'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'PUT' }, status=400) def test_preflight_credentials_are_supported(self): resp = self.app.options('/spam', headers={ 'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'GET' }) self.assertIn('Access-Control-Allow-Credentials', resp.headers) self.assertEquals(resp.headers['Access-Control-Allow-Credentials'], 'true') def test_preflight_credentials_header_not_included_when_not_needed(self): resp = self.app.options('/spam', headers={ 'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'POST' }) self.assertNotIn('Access-Control-Allow-Credentials', resp.headers) def test_preflight_contains_max_age(self): resp = self.app.options('/spam', headers={ 'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'GET' }) self.assertIn('Access-Control-Max-Age', resp.headers) self.assertEquals(resp.headers['Access-Control-Max-Age'], '42') def test_resp_dont_include_allow_origin(self): resp = self.app.get('/squirel') # omit the Origin header self.assertNotIn('Access-Control-Allow-Origin', resp.headers) self.assertEquals(resp.json, 'squirels') def test_responses_include_an_allow_origin_header(self): resp = self.app.get('/squirel', headers={'Origin': 'notmyidea.org'}) self.assertIn('Access-Control-Allow-Origin', resp.headers) self.assertEquals(resp.headers['Access-Control-Allow-Origin'], 'notmyidea.org') def test_credentials_are_included(self): resp = self.app.get('/spam', headers={'Origin': 'notmyidea.org'}) self.assertIn('Access-Control-Allow-Credentials', resp.headers) self.assertEquals(resp.headers['Access-Control-Allow-Credentials'], 'true') def test_headers_are_exposed(self): resp = self.app.get('/squirel', headers={'Origin': 'notmyidea.org'}) self.assertIn('Access-Control-Expose-Headers', resp.headers) headers = resp.headers['Access-Control-Expose-Headers'].split(',') self.assertIn('X-My-Header', headers) def test_preflight_request_headers_are_included(self): resp = self.app.options('/squirel', headers={ 'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'GET', 'Access-Control-Request-Headers': 'foo,bar,baz' }) # per default, they should be authorized, and returned in the list of # authorized headers headers = resp.headers['Access-Control-Allow-Headers'].split(',') self.assertIn('foo', headers) self.assertIn('bar', headers) self.assertIn('baz', headers) def test_preflight_request_headers_isnt_too_permissive(self): self.app.options('/eggs', headers={ 'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'GET', 'Access-Control-Request-Headers': 'foo,bar,baz' }, status=400) def test_preflight_headers_arent_case_sensitive(self): self.app.options('/spam', headers={ 'Origin': 'notmyidea.org', 'Access-Control-Request-Method': 'GET', 'Access-Control-Request-Headers': 'x-my-header', }) def test_400_returns_CORS_headers(self): resp = self.app.get('/bacon/not', status=400, headers={'Origin': 'notmyidea.org'}) self.assertIn('Access-Control-Allow-Origin', resp.headers) def test_404_returns_CORS_headers(self): resp = self.app.get('/bacon/notgood', status=404, headers={'Origin': 'notmyidea.org'}) self.assertIn('Access-Control-Allow-Origin', resp.headers) def test_existing_non_service_route(self): resp = self.app.get('/noservice', status=200, headers={'Origin': 'notmyidea.org'}) self.assertEquals(resp.body, b'No Service here.')
class ViewIntegrationTest(unittest.TestCase): httpd = None executor = None @classmethod def setUpClass(cls) -> None: cls.httpd = HTTPServer(('localhost', 8080), SimpleHTTPRequestHandler) cls.executor = ThreadPoolExecutor(max_workers=1) cls.executor.submit(cls.httpd.serve_forever) @classmethod def tearDownClass(cls) -> None: cls.httpd.shutdown() cls.executor.shutdown() def setUp(self): settings = { 'ds.host': 'http://localhost:8080', 'ds.file.prefix': 'ds-preview-', 'ds.document.meta.path': '/api/index/search/%s/doc/%s', 'ds.document.src.path': '/api/%s/documents/src/%s', 'ds.document.max.size': '50000000', 'ds.document.max.age': '259200', 'ds.session.cookie.enabled': 'true', 'ds.session.cookie.name': '_ds_session_id', 'ds.session.header.enabled': 'true', 'ds.session.header.name': 'X-Ds-Session-Id', } app = main({}, **settings) self.app = TestApp(app) def test_home_page(self): response = self.app.get('/') self.assertIn(b'Datashare preview', response.body) def test_cors_thumbnail_preflight(self): self._assert_cors_headers_ok( self.app.options('/api/v1/thumbnail/index/id')) def test_cors_info_preflight(self): self._assert_cors_headers_ok( self.app.options('/api/v1/thumbnail/index/id.json')) def test_thumbnail_with_neither_cookie_nor_header(self): response = self.app.get('/api/v1/thumbnail/index/id', expect_errors=True) self.assertEqual(response.status, '401 Unauthorized') def test_thumbnail_with_cookie(self): create_file_ondisk_from_resource('dummy.jpg', '/tmp/ds-preview--index-id') response = self.app.get('/api/v1/thumbnail/index/id', headers=auth_headers()) self.assertEqual(response.status, '200 OK') def test_thumbnail_with_header(self): create_file_ondisk_from_resource('dummy.jpg', '/tmp/ds-preview--index-id') response = self.app.get('/api/v1/thumbnail/index/id', headers=auth_headers()) self.assertEqual(response.status, '200 OK') def test_info_json(self): create_file_ondisk_from_resource('dummy.jpg', '/tmp/ds-preview--index-id') response = self.app.get('/api/v1/thumbnail/index/id.json', headers=auth_headers()) self.assertEqual(response.status, '200 OK') self.assertEqual(response.headers['Content-Type'], 'application/json') self.assertEqual( json.loads(response.body.decode()), { "previewable": True, "pages": 1, "content": None, "content_type": "image/jpeg" }) def test_ods_json(self): create_file_ondisk_from_resource('dummy.ods', '/tmp/ds-preview--index-id') response = self.app.get( '/api/v1/thumbnail/index/id.json?include-content=1', headers=auth_headers()) self.assertEqual(response.status, '200 OK') self.assertEqual(response.headers['Content-Type'], 'application/json') self.assertEqual( json.loads(response.body.decode()), { 'content': { 'meals': [['name'], ['couscous'], ['hummus'], ['paella']], 'people': [['firstname', 'lastname'], ['foo', 'bar']] }, 'content_type': 'application/vnd.oasis.opendocument.spreadsheet', 'pages': 2, 'previewable': True }) def _assert_cors_headers_ok(self, response): self.assertEqual('*', response.headers.get('Access-Control-Allow-Origin')) self.assertEqual('GET', response.headers.get('Access-Control-Allow-Methods')) self.assertEqual('x-ds-session-id', response.headers.get('Access-Control-Allow-Headers'))
class TestHttplib(unittest.TestCase): client = 'httplib' client_options = {} def setUp(self): self.server = StopableWSGIServer.create(debug_app) self.application_url = self.server.application_url.rstrip('/') self.proxy = proxies.HostProxy(self.application_url, client=self.client, **self.client_options) self.app = TestApp(self.proxy) def test_form(self): resp = self.app.get('/form.html') resp.mustcontain('</form>') form = resp.form form['name'] = 'gawel' resp = form.submit() resp.mustcontain('name=gawel') def test_head(self): resp = self.app.head('/form.html') self.assertEqual(resp.status_int, 200) self.assertEqual(len(resp.body), 0) def test_webob_error(self): req = Request.blank('/') req.content_length = '-1' resp = req.get_response(self.proxy) self.assertEqual(resp.status_int, 500, resp) def test_not_allowed_method(self): resp = self.app.options('/', status='*') self.assertEqual(resp.status_int, 405) def test_status(self): resp = self.app.get('/?status=404', status='*') self.assertEqual(resp.status_int, 404) def test_redirect(self): location = self.application_url + '/form.html' resp = self.app.get('/?status=301%20Redirect&header-location=' + location, status='*') self.assertEqual(resp.status_int, 301, resp) self.assertEqual(resp.location, location) location = 'http://foo.com' resp = self.app.get('/?status=301%20Redirect&header-location=' + location, status='*') self.assertEqual(resp.status_int, 301, resp) self.assertEqual(resp.location, location) location = '/foo' resp = self.app.get('/?status=301%20Redirect&header-location=' + location, status='*') self.assertEqual(resp.status_int, 301, resp) self.assertEqual(resp.location, self.application_url + location) location = self.application_url + '/script_name/form.html' self.proxy.strip_script_name = False resp = self.app.get('/?status=301%20Redirect&header-Location=' + location, status='*', extra_environ={'SCRIPT_NAME': '/script_name'}) self.assertEqual(resp.status_int, 301, resp) self.assertEqual(resp.location, location) def test_chunked(self): resp = self.app.get('/', headers=[('Transfer-Encoding', 'chunked')]) resp.mustcontain(no='chunked') def tearDown(self): self.server.shutdown()