def test_resource_colander_validation_error(): error = {'error': {'code': 400, 'message': '400 Bad Request'}} app = App() c = Client(app) response = c.put_json('/', {'password': '******'}, status=400) assert response.json == error response = c.patch_json('/', {'password': '******'}, status=400) assert response.json == error
def test_resource_marshmallow_validation(): app = App() c = Client(app) response = c.put_json('/', { 'email': '*****@*****.**', 'password': '******' }) assert response.json == {} response = c.patch_json('/', {'email': '*****@*****.**'}) assert response.json == {}
def test_resource_marshmallow_validation_error(): error = { 'error': { 'code': 400, 'message': { 'email': ['Missing data for required field.'] } } } app = App() c = Client(app) response = c.put_json('/', {'password': '******'}, status=400) assert response.json == error response = c.patch_json('/', {'password': '******'}, status=400) assert response.json == error
class TestIntegration(unittest.TestCase): def setUp(self): app = Application('tangled.web.tests:test.ini') app.mount_resource('user', UserResource, '/users/<id>') self.app = TestApp(app) self._original_data = copy.deepcopy(Users.data) def tearDown(self): Users.data = self._original_data def test_get(self): self.assertEqual(Users.get(1)['name'], 'Alice') response = self.app.get('/users/1') self.assertEqual(response.status_code, 200) self.assertEqual(response.json['name'], 'Alice') self.assertEqual(Users.get(1)['name'], 'Alice') def test_put(self): self.assertEqual(Users.get(2)['name'], 'Bob') response = self.app.put('/users/2', params={'name': 'Bobby'}) self.assertEqual(response.status_code, 200) self.assertEqual(response.json['name'], 'Bobby') self.assertEqual(Users.get(2)['name'], 'Bobby') def test_patch(self): self.assertEqual(Users.get(2)['name'], 'Bob') response = self.app.patch('/users/2', params={'name': 'Bobby'}) self.assertEqual(response.status_code, 200) self.assertEqual(response.json['name'], 'Bobby') self.assertEqual(Users.get(2)['name'], 'Bobby') def test_patch_json(self): self.assertEqual(Users.get(2)['name'], 'Bob') response = self.app.patch_json('/users/2', params={'name': 'Bobby'}) self.assertEqual(response.status_code, 200) self.assertEqual(response.json['name'], 'Bobby') self.assertEqual(Users.get(2)['name'], 'Bobby')
class Modificacion(TestCase): """ NOTA: Sobre la validación de datos, testar directamente nuestra pequeña clase TODO: Validar cambio de grupo principal TODO: Validar cambio de estado de la cuenta TODO: Validar cambios de grupos TODO: Validar cambio de contraseña TODO: Validar que tan pocas claves podemos cambiar """ @classmethod def setUpClass(self): # Cargamos los datos entidad = cargar_datos('usuario')[2] self.uid = entidad['uid'] self.datos = {'corpus': entidad} # Trabajamos en obtener un token self.token = cargar_credenciales() # Creamos nuestro objeto para pruebas from justine import main from webtest import TestApp app = main({}) self.testapp = TestApp(app) res = self.testapp.post_json('/usuarios', status=201, params=self.datos, headers=self.token) @classmethod def tearDownClass(self): res = self.testapp.head('/usuarios/' + self.uid, status="*", headers=self.token) if res.status_int == 200: self.testapp.delete('/usuarios/' + self.uid, status=200, headers=self.token) def test_actualizacion(self): self.datos['corpus']['title'] = "Titulador" self.testapp.patch_json('/usuarios/' + self.uid, status=200, params=self.datos, headers=self.token) res = self.testapp.get('/usuarios/' + self.uid, status=200, headers=self.token) respuesta = res.json_body['mensaje'][0]['title'] datos = self.datos['corpus']['title'] self.assertEqual(respuesta, datos) def test_actualizacion_displayName(self): sn = "Sotomayor" givenName = self.datos['corpus']['givenName'] displayName = givenName + " " + sn self.datos['corpus']['sn'] = sn self.testapp.patch_json('/usuarios/' + self.uid, status=200, params=self.datos, headers=self.token) res = self.testapp.get('/usuarios/' + self.uid, status=200, headers=self.token) respuesta = res.json_body['mensaje'][0]['displayName'] self.assertEqual(respuesta, displayName) def test_corpus_faltante(self): datos = {'cuerpo': self.datos['corpus'].copy()} self.testapp.patch_json('/usuarios/' + self.uid, status=400, params=datos, headers=self.token) def test_json_malformateado(self): datos = "Mínimo esfuerzo para máximo daño" self.testapp.patch_json('/usuarios/' + self.uid, status=400, params=datos, headers=self.token) def test_noexistente(self): uid = 'fitzcarraldo' datos = {'corpus': self.datos['corpus'].copy()} datos['corpus']['uid'] = uid self.testapp.patch_json('/usuarios/' + uid, status=404, params=datos, headers=self.token) def test_claves_incompletas(self): cuerpo = self.datos['corpus'].copy() del cuerpo['sn'] del cuerpo['givenName'] datos = {'corpus': cuerpo} self.testapp.patch_json('/usuarios/' + self.uid, status=200, params=datos, headers=self.token) def test_actualizacion_noauth(self): self.testapp.patch_json('/usuarios/' + self.uid, status=403, params=self.datos)
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 ApiTestCase(TestCase): def setUp(self): super(ApiTestCase, self).setUp() repo_store = self.useFixture(TempDir()).path self.useFixture(EnvironmentVariable("REPO_STORE", repo_store)) self.app = TestApp(api.main({})) self.repo_path = uuid.uuid1().hex self.repo_store = os.path.join(repo_store, self.repo_path) self.repo_root = repo_store self.commit = {'ref': 'refs/heads/master', 'message': 'test commit.'} self.tag = {'ref': 'refs/tags/tag0', 'message': 'tag message'} def assertReferencesEqual(self, repo, expected, observed): self.assertEqual( repo.lookup_reference(expected).peel().oid, repo.lookup_reference(observed).peel().oid) def get_ref(self, ref): resp = self.app.get('/repo/{}/{}'.format(self.repo_path, ref)) return resp.json def test_repo_init(self): resp = self.app.post_json('/repo', {'repo_path': self.repo_path}) self.assertIn(self.repo_path, resp.json['repo_url']) self.assertEqual(200, resp.status_code) def test_repo_init_with_invalid_repo_path(self): resp = self.app.post_json('/repo', {'repo_path': '../1234'}, expect_errors=True) self.assertEqual(404, resp.status_code) def test_repo_init_with_existing_repo(self): """Repo can be not be initialised with existing path.""" factory = RepoFactory(self.repo_store) repo_path = os.path.basename(os.path.normpath(factory.repo_path)) resp = self.app.post_json('/repo', {'repo_path': repo_path}, expect_errors=True) self.assertEqual(409, resp.status_code) def test_repo_init_with_clone(self): """Repo can be initialised with optional clone.""" factory = RepoFactory(self.repo_store, num_commits=2) factory.build() new_repo_path = uuid.uuid1().hex resp = self.app.post_json('/repo', {'repo_path': new_repo_path, 'clone_from': self.repo_path, 'clone_refs': True}) repo1_revlist = get_revlist(factory.repo) clone_from = resp.json['repo_url'].split('/')[-1] repo2 = open_repo(os.path.join(self.repo_root, clone_from)) repo2_revlist = get_revlist(repo2) self.assertEqual(repo1_revlist, repo2_revlist) self.assertEqual(200, resp.status_code) self.assertIn(new_repo_path, resp.json['repo_url']) def test_repo_get(self): """The GET method on a repository returns its properties.""" factory = RepoFactory(self.repo_store, num_branches=2, num_commits=1) factory.build() factory.repo.set_head('refs/heads/branch-0') resp = self.app.get('/repo/{}'.format(self.repo_path)) self.assertEqual(200, resp.status_code) self.assertEqual({'default_branch': 'refs/heads/branch-0'}, resp.json) def test_repo_get_default_branch_missing(self): """default_branch is returned even if that branch has been deleted.""" factory = RepoFactory(self.repo_store, num_branches=2, num_commits=1) factory.build() factory.repo.set_head('refs/heads/branch-0') factory.repo.lookup_reference('refs/heads/branch-0').delete() resp = self.app.get('/repo/{}'.format(self.repo_path)) self.assertEqual(200, resp.status_code) self.assertEqual({'default_branch': 'refs/heads/branch-0'}, resp.json) def test_repo_patch_default_branch(self): """A repository's default branch ("HEAD") can be changed.""" factory = RepoFactory(self.repo_store, num_branches=2, num_commits=1) factory.build() factory.repo.set_head('refs/heads/branch-0') self.assertReferencesEqual(factory.repo, 'refs/heads/branch-0', 'HEAD') resp = self.app.patch_json( '/repo/{}'.format(self.repo_path), {'default_branch': 'refs/heads/branch-1'}) self.assertEqual(204, resp.status_code) self.assertReferencesEqual(factory.repo, 'refs/heads/branch-1', 'HEAD') def test_cross_repo_merge_diff(self): """Merge diff can be requested across 2 repositories.""" factory = RepoFactory(self.repo_store) c1 = factory.add_commit('foo', 'foobar.txt') repo2_name = uuid.uuid4().hex factory2 = RepoFactory( os.path.join(self.repo_root, repo2_name), clone_from=factory) c2 = factory.add_commit('bar', 'foobar.txt', parents=[c1]) c3 = factory2.add_commit('baz', 'foobar.txt', parents=[c1]) resp = self.app.get('/repo/{}:{}/compare-merge/{}:{}'.format( self.repo_path, repo2_name, c2, c3)) self.assertIn('-bar', resp.json['patch']) def test_cross_repo_diff(self): """Diff can be requested across 2 repositories.""" factory = RepoFactory(self.repo_store) c1 = factory.add_commit('foo', 'foobar.txt') repo2_name = uuid.uuid4().hex factory2 = RepoFactory( os.path.join(self.repo_root, repo2_name), clone_from=factory) c2 = factory.add_commit('bar', 'foobar.txt', parents=[c1]) c3 = factory2.add_commit('baz', 'foobar.txt', parents=[c1]) resp = self.app.get('/repo/{}:{}/compare/{}..{}'.format( self.repo_path, repo2_name, c2, c3)) self.assertIn('-bar', resp.json['patch']) self.assertIn('+baz', resp.json['patch']) def test_cross_repo_diff_invalid_repo(self): """Cross repo diff with invalid repo returns HTTP 404.""" resp = self.app.get('/repo/1:2/compare-merge/3:4', expect_errors=True) self.assertEqual(404, resp.status_code) def test_cross_repo_diff_invalid_commit(self): """Cross repo diff with an invalid commit returns HTTP 404.""" factory = RepoFactory(self.repo_store) c1 = factory.add_commit('foo', 'foobar.txt') repo2_name = uuid.uuid4().hex RepoFactory( os.path.join(self.repo_root, repo2_name), clone_from=factory) c2 = factory.add_commit('bar', 'foobar.txt', parents=[c1]) resp = self.app.get('/repo/{}:{}/diff/{}:{}'.format( self.repo_path, repo2_name, c2, 'invalid'), expect_errors=True) self.assertEqual(404, resp.status_code) def test_repo_delete(self): self.app.post_json('/repo', {'repo_path': self.repo_path}) resp = self.app.delete('/repo/{}'.format(self.repo_path)) self.assertEqual(200, resp.status_code) self.assertFalse(os.path.exists(self.repo_store)) def test_repo_get_refs(self): """Ensure expected ref objects are returned and shas match.""" ref = self.commit.get('ref') repo = RepoFactory(self.repo_store, num_commits=1, num_tags=1).build() resp = self.app.get('/repo/{}/refs'.format(self.repo_path)) body = resp.json self.assertTrue(ref in body) self.assertTrue(self.tag.get('ref') in body) oid = repo.head.get_object().oid.hex # git object sha resp_sha = body[ref]['object'].get('sha1') self.assertEqual(oid, resp_sha) def test_repo_get_refs_nonexistent(self): """get_refs on a non-existent repository returns HTTP 404.""" resp = self.app.get('/repo/1/refs', expect_errors=True) self.assertEqual(404, resp.status_code) def test_ignore_non_unicode_refs(self): """Ensure non-unicode refs are dropped from ref collection.""" factory = RepoFactory(self.repo_store) commit_oid = factory.add_commit('foo', 'foobar.txt') tag = '\xe9\xe9\xe9' # latin-1 tag_message = 'tag message' factory.add_tag(tag, tag_message, commit_oid) resp = self.app.get('/repo/{}/refs'.format(self.repo_path)) refs = resp.json self.assertEqual(1, len(refs.keys())) def test_allow_unicode_refs(self): """Ensure unicode refs are included in ref collection.""" factory = RepoFactory(self.repo_store) commit_oid = factory.add_commit('foo', 'foobar.txt') tag = u'おいしいイカ'.encode('utf-8') tag_message = u'かわいい タコ'.encode('utf-8') factory.add_tag(tag, tag_message, commit_oid) resp = self.app.get('/repo/{}/refs'.format(self.repo_path)) refs = resp.json self.assertEqual(2, len(refs.keys())) def test_repo_get_ref(self): RepoFactory(self.repo_store, num_commits=1).build() ref = 'refs/heads/master' resp = self.get_ref(ref) self.assertTrue(ref in resp) def test_repo_get_ref_nonexistent_repository(self): """get_ref on a non-existent repository returns HTTP 404.""" resp = self.app.get('/repo/1/refs/heads/master', expect_errors=True) self.assertEqual(404, resp.status_code) def test_repo_get_ref_nonexistent_ref(self): """get_ref on a non-existent ref in a repository returns HTTP 404.""" RepoFactory(self.repo_store, num_commits=1).build() resp = self.app.get( '/repo/{}/refs/heads/master'.format(self.repo_path)) self.assertEqual(200, resp.status_code) resp = self.app.get( '/repo/{}/refs/heads/nonexistent'.format(self.repo_path), expect_errors=True) self.assertEqual(404, resp.status_code) def test_repo_get_unicode_ref(self): factory = RepoFactory(self.repo_store) commit_oid = factory.add_commit('foo', 'foobar.txt') tag_name = u'☃'.encode('utf-8') tag_message = u'☃'.encode('utf-8') factory.add_tag(tag_name, tag_message, commit_oid) tag = 'refs/tags/{}'.format(tag_name) resp = self.get_ref(tag) self.assertTrue(tag.decode('utf-8') in resp) def test_repo_get_tag(self): RepoFactory(self.repo_store, num_commits=1, num_tags=1).build() tag = self.tag.get('ref') resp = self.get_ref(tag) self.assertTrue(tag in resp) def test_repo_compare_commits(self): """Ensure expected changes exist in diff patch.""" repo = RepoFactory(self.repo_store) c1_oid = repo.add_commit('foo', 'foobar.txt') c2_oid = repo.add_commit('bar', 'foobar.txt', parents=[c1_oid]) path = '/repo/{}/compare/{}..{}'.format(self.repo_path, c1_oid, c2_oid) resp = self.app.get(path) self.assertIn('-foo', resp.body) self.assertIn('+bar', resp.body) def test_repo_diff_commits(self): """Ensure expected commits objects are returned in diff.""" repo = RepoFactory(self.repo_store) c1_oid = repo.add_commit('foo', 'foobar.txt') c2_oid = repo.add_commit('bar', 'foobar.txt', parents=[c1_oid]) path = '/repo/{}/compare/{}..{}'.format(self.repo_path, c1_oid, c2_oid) resp = self.app.get(path) self.assertIn(c1_oid.hex, resp.json['commits'][0]['sha1']) self.assertIn(c2_oid.hex, resp.json['commits'][1]['sha1']) def test_repo_diff_unicode_commits(self): """Ensure expected utf-8 commits objects are returned in diff.""" factory = RepoFactory(self.repo_store) message = u'屋漏偏逢连夜雨'.encode('utf-8') message2 = u'说曹操,曹操到'.encode('utf-8') oid = factory.add_commit(message, 'foo.py') oid2 = factory.add_commit(message2, 'bar.py', [oid]) resp = self.app.get('/repo/{}/compare/{}..{}'.format( self.repo_path, oid, oid2)) self.assertEqual(resp.json['commits'][0]['message'], message.decode('utf-8')) self.assertEqual(resp.json['commits'][1]['message'], message2.decode('utf-8')) def test_repo_diff_non_unicode_commits(self): """Ensure non utf-8 chars are handled but stripped from diff.""" factory = RepoFactory(self.repo_store) message = 'not particularly sensible latin-1: \xe9\xe9\xe9.' oid = factory.add_commit(message, 'foo.py') oid2 = factory.add_commit('a sensible commit message', 'foo.py', [oid]) resp = self.app.get('/repo/{}/compare/{}..{}'.format( self.repo_path, oid, oid2)) self.assertEqual(resp.json['commits'][0]['message'], message.decode('utf-8', 'replace')) def test_repo_get_diff_nonexistent_sha1(self): """get_diff on a non-existent sha1 returns HTTP 404.""" RepoFactory(self.repo_store).build() resp = self.app.get('/repo/{}/compare/1..2'.format( self.repo_path), expect_errors=True) self.assertEqual(404, resp.status_code) def test_repo_get_diff_invalid_separator(self): """get_diff with an invalid separator (not ../...) returns HTTP 404.""" RepoFactory(self.repo_store).build() resp = self.app.get('/repo/{}/compare/1++2'.format( self.repo_path), expect_errors=True) self.assertEqual(400, resp.status_code) def test_repo_common_ancestor_diff(self): """Ensure expected changes exist in diff patch.""" repo = RepoFactory(self.repo_store) c1 = repo.add_commit('foo', 'foobar.txt') c2_right = repo.add_commit('bar', 'foobar.txt', parents=[c1]) c3_right = repo.add_commit('baz', 'foobar.txt', parents=[c2_right]) c2_left = repo.add_commit('qux', 'foobar.txt', parents=[c1]) c3_left = repo.add_commit('corge', 'foobar.txt', parents=[c2_left]) resp = self.app.get('/repo/{}/compare/{}...{}'.format( self.repo_path, c3_left, c3_right)) self.assertIn('-foo', resp.json_body['patch']) self.assertIn('+baz', resp.json_body['patch']) self.assertNotIn('+corge', resp.json_body['patch']) def test_repo_diff_empty(self): """Ensure that diffing two identical commits returns an empty string as the patch, not None.""" repo = RepoFactory(self.repo_store) c1 = repo.add_commit('foo\n', 'blah.txt') resp = self.app.get('/repo/{}/compare/{}..{}'.format( self.repo_path, c1, c1)) self.assertEqual('', resp.json_body['patch']) def test_repo_diff_merge(self): """Ensure expected changes exist in diff patch.""" repo = RepoFactory(self.repo_store) c1 = repo.add_commit('foo\nbar\nbaz\n', 'blah.txt') c2_right = repo.add_commit('quux\nbar\nbaz\n', 'blah.txt', parents=[c1]) c3_right = repo.add_commit('quux\nbar\nbaz\n', 'blah.txt', parents=[c2_right]) c2_left = repo.add_commit('foo\nbar\nbar\n', 'blah.txt', parents=[c1]) c3_left = repo.add_commit('foo\nbar\nbar\n', 'blah.txt', parents=[c2_left]) resp = self.app.get('/repo/{}/compare-merge/{}:{}'.format( self.repo_path, c3_right, c3_left)) self.assertIn(' quux', resp.json_body['patch']) self.assertIn('-baz', resp.json_body['patch']) self.assertIn('+bar', resp.json_body['patch']) self.assertNotIn('foo', resp.json_body['patch']) self.assertEqual([], resp.json_body['conflicts']) def test_repo_diff_merge_with_conflicts(self): """Ensure that compare-merge returns conflicts information.""" repo = RepoFactory(self.repo_store) c1 = repo.add_commit('foo\n', 'blah.txt') c2_left = repo.add_commit('foo\nbar\n', 'blah.txt', parents=[c1]) c2_right = repo.add_commit('foo\nbaz\n', 'blah.txt', parents=[c1]) resp = self.app.get('/repo/{}/compare-merge/{}:{}'.format( self.repo_path, c2_left, c2_right)) self.assertIn(dedent("""\ +<<<<<<< blah.txt bar +======= +baz +>>>>>>> blah.txt """), resp.json_body['patch']) self.assertEqual(['blah.txt'], resp.json_body['conflicts']) def test_repo_diff_merge_empty(self): """Ensure that diffing two identical commits returns an empty string as the patch, not None.""" repo = RepoFactory(self.repo_store) c1 = repo.add_commit('foo\n', 'blah.txt') resp = self.app.get('/repo/{}/compare-merge/{}:{}'.format( self.repo_path, c1, c1)) self.assertEqual('', resp.json_body['patch']) def test_repo_get_commit(self): factory = RepoFactory(self.repo_store) message = 'Computers make me angry.' commit_oid = factory.add_commit(message, 'foobar.txt') resp = self.app.get('/repo/{}/commits/{}'.format( self.repo_path, commit_oid.hex)) commit_resp = resp.json self.assertEqual(commit_oid.hex, commit_resp['sha1']) self.assertEqual(message, commit_resp['message']) def test_repo_get_commit_nonexistent(self): """Trying to get a non-existent OID returns HTTP 404.""" factory = RepoFactory(self.repo_store) resp = self.app.get('/repo/{}/commits/{}'.format( self.repo_path, factory.nonexistent_oid()), expect_errors=True) self.assertEqual(404, resp.status_code) def test_repo_get_non_commit(self): """Trying to get a non-commit returns HTTP 404.""" factory = RepoFactory(self.repo_store, num_commits=1) factory.build() tree_oid = factory.repo[factory.commits[0]].tree.hex resp = self.app.get('/repo/{}/commits/{}'.format( self.repo_path, tree_oid), expect_errors=True) self.assertEqual(404, resp.status_code) def test_repo_get_commit_collection(self): """Ensure commits can be returned in bulk.""" factory = RepoFactory(self.repo_store, num_commits=10) factory.build() bulk_commits = {'commits': [c.hex for c in factory.commits[0::2]]} resp = self.app.post_json('/repo/{}/commits'.format( self.repo_path), bulk_commits) self.assertEqual(5, len(resp.json)) self.assertEqual(bulk_commits['commits'][0], resp.json[0]['sha1']) def test_repo_get_commit_collection_ignores_errors(self): """Non-existent OIDs and non-commits in a collection are ignored.""" factory = RepoFactory(self.repo_store, num_commits=10) factory.build() bulk_commits = { 'commits': [ factory.commits[0].hex, factory.repo[factory.commits[0]].tree.hex, factory.nonexistent_oid(), ], } resp = self.app.post_json( '/repo/{}/commits'.format(self.repo_path), bulk_commits) self.assertEqual(1, len(resp.json)) self.assertEqual(bulk_commits['commits'][0], resp.json[0]['sha1']) def test_repo_get_log_signatures(self): """Ensure signatures are correct.""" factory = RepoFactory(self.repo_store) committer = factory.makeSignature(u'村上 春樹'.encode('utf-8'), u'tsukuru@猫の町.co.jp'.encode('utf-8'), encoding='utf-8') author = factory.makeSignature( u'Владимир Владимирович Набоков'.encode('utf-8'), u'Набоко@zembla.ru'.encode('utf-8'), encoding='utf-8') oid = factory.add_commit('Obfuscate colophon.', 'path.foo', author=author, committer=committer) resp = self.app.get('/repo/{}/log/{}'.format(self.repo_path, oid)) self.assertEqual(author.name, resp.json[0]['author']['name']) def test_repo_get_log(self): factory = RepoFactory(self.repo_store, num_commits=4) factory.build() commits_from = factory.commits[2].hex resp = self.app.get('/repo/{}/log/{}'.format( self.repo_path, commits_from)) self.assertEqual(3, len(resp.json)) def test_repo_get_unicode_log(self): factory = RepoFactory(self.repo_store) message = u'나는 김치 사랑'.encode('utf-8') message2 = u'(╯°□°)╯︵ ┻━┻'.encode('utf-8') oid = factory.add_commit(message, '자장면/짜장면.py') oid2 = factory.add_commit(message2, '엄마야!.js', [oid]) resp = self.app.get('/repo/{}/log/{}'.format(self.repo_path, oid2)) self.assertEqual(message2.decode('utf-8', 'replace'), resp.json[0]['message']) self.assertEqual(message.decode('utf-8', 'replace'), resp.json[1]['message']) def test_repo_get_non_unicode_log(self): """Ensure that non-unicode data is discarded.""" factory = RepoFactory(self.repo_store) message = '\xe9\xe9\xe9' # latin-1 oid = factory.add_commit(message, 'foo.py') resp = self.app.get('/repo/{}/log/{}'.format(self.repo_path, oid)) self.assertEqual(message.decode('utf-8', 'replace'), resp.json[0]['message']) def test_repo_get_log_with_limit(self): """Ensure the commit log can filtered by limit.""" factory = RepoFactory(self.repo_store, num_commits=10) repo = factory.build() head = repo.head.target resp = self.app.get('/repo/{}/log/{}?limit=5'.format( self.repo_path, head)) self.assertEqual(5, len(resp.json)) def test_repo_get_log_with_stop(self): """Ensure the commit log can be filtered by a stop commit.""" factory = RepoFactory(self.repo_store, num_commits=10) repo = factory.build() stop_commit = factory.commits[4] excluded_commit = factory.commits[5] head = repo.head.target resp = self.app.get('/repo/{}/log/{}?stop={}'.format( self.repo_path, head, stop_commit)) self.assertEqual(5, len(resp.json)) self.assertNotIn(excluded_commit, resp.json) def test_repo_repack_verify_pack(self): """Ensure commit exists in pack.""" factory = RepoFactory(self.repo_store) oid = factory.add_commit('foo', 'foobar.txt') resp = self.app.post_json('/repo/{}/repack'.format(self.repo_path), {'prune': True, 'single': True}) for filename in factory.packs: pack = os.path.join(factory.pack_dir, filename) out = subprocess.check_output(['git', 'verify-pack', pack, '-v']) self.assertEqual(200, resp.status_code) self.assertIn(oid.hex, out) def test_repo_repack_verify_commits_to_pack(self): """Ensure commits in different packs exist in merged pack.""" factory = RepoFactory(self.repo_store) oid = factory.add_commit('foo', 'foobar.txt') with chdir(factory.pack_dir): subprocess.call(['git', 'gc', '-q']) # pack first commit oid2 = factory.add_commit('bar', 'foobar.txt', [oid]) p = subprocess.Popen(['git', 'pack-objects', '-q', 'pack2'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) p.communicate(input=oid2.hex) self.assertEqual(2, len(factory.packs)) # ensure 2 packs exist self.app.post_json('/repo/{}/repack'.format(self.repo_path), {'prune': True, 'single': True}) self.assertEqual(1, len(factory.packs)) repacked_pack = os.path.join(factory.pack_dir, factory.packs[0]) out = subprocess.check_output(['git', 'verify-pack', repacked_pack, '-v']) self.assertIn(oid.hex, out) self.assertIn(oid2.hex, out) def test_repo_detect_merges_missing_target(self): """A non-existent target OID returns HTTP 404.""" factory = RepoFactory(self.repo_store) resp = self.app.post_json('/repo/{}/detect-merges/{}'.format( self.repo_path, factory.nonexistent_oid()), {'sources': []}, expect_errors=True) self.assertEqual(404, resp.status_code) def test_repo_detect_merges_missing_source(self): """A non-existent source commit is ignored.""" factory = RepoFactory(self.repo_store) # A---B a = factory.add_commit('a\n', 'file') b = factory.add_commit('b\n', 'file', parents=[a]) resp = self.app.post_json('/repo/{}/detect-merges/{}'.format( self.repo_path, b), {'sources': [factory.nonexistent_oid()]}) self.assertEqual(200, resp.status_code) self.assertEqual({}, resp.json) def test_repo_detect_merges_unmerged(self): """An unmerged commit is not returned.""" factory = RepoFactory(self.repo_store) # A---B # \ # C a = factory.add_commit('a\n', 'file') b = factory.add_commit('b\n', 'file', parents=[a]) c = factory.add_commit('c\n', 'file', parents=[a]) resp = self.app.post_json('/repo/{}/detect-merges/{}'.format( self.repo_path, b), {'sources': [c.hex]}) self.assertEqual(200, resp.status_code) self.assertEqual({}, resp.json) def test_repo_detect_merges_pulled(self): """Commits that were pulled (fast-forward) are their own merge points.""" factory = RepoFactory(self.repo_store) # A---B---C a = factory.add_commit('a\n', 'file') b = factory.add_commit('b\n', 'file', parents=[a]) c = factory.add_commit('c\n', 'file', parents=[b]) # The start commit would never be the source of a merge proposal, # but include it anyway to test boundary conditions. resp = self.app.post_json('/repo/{}/detect-merges/{}'.format( self.repo_path, c), {'sources': [a.hex, b.hex, c.hex]}) self.assertEqual(200, resp.status_code) self.assertEqual({a.hex: a.hex, b.hex: b.hex, c.hex: c.hex}, resp.json) def test_repo_detect_merges_merged(self): """Commits that were merged have sensible merge points.""" factory = RepoFactory(self.repo_store) # A---C---D---G---H # \ / / # B---E---F---I a = factory.add_commit('a\n', 'file') b = factory.add_commit('b\n', 'file', parents=[a]) c = factory.add_commit('c\n', 'file', parents=[a, b]) d = factory.add_commit('d\n', 'file', parents=[c]) e = factory.add_commit('e\n', 'file', parents=[b]) f = factory.add_commit('f\n', 'file', parents=[e]) g = factory.add_commit('g\n', 'file', parents=[d, f]) h = factory.add_commit('h\n', 'file', parents=[g]) i = factory.add_commit('i\n', 'file', parents=[f]) resp = self.app.post_json('/repo/{}/detect-merges/{}'.format( self.repo_path, h), {'sources': [b.hex, e.hex, i.hex]}) self.assertEqual(200, resp.status_code) self.assertEqual({b.hex: c.hex, e.hex: g.hex}, resp.json)
class FlaskAdapterTests(unittest.TestCase): def setUp(self): from tests.example_app.flask_app import create_pale_flask_app self.flask_app = create_pale_flask_app() self.app = TestApp(self.flask_app) def assertExpectedFields(self, returned_dict, expected_fields): d = returned_dict.copy() # don't clobber the input for f in expected_fields: self.assertIn(f, d) val = d.pop(f) # don't check the val for now # make sure there's nothing extraneous left in the dict self.assertEqual(len(d.keys()), 0) return def test_successful_get_with_route_args(self): now = datetime.datetime.utcnow() resp = self.app.get('/api/arg_test/arg-a/arg-b') self.assertEqual(resp.status_code, 200) self.assertEqual(resp.json, { "arg_a": "arg-a", "arg_b": "arg-b", }) def test_successful_get_without_params(self): now = datetime.datetime.utcnow() resp = self.app.get('/api/time/current') self.assertEqual(resp.status_code, 200) # Test _after_response_handlers self.assertIn("After-Response", resp.headers) self.assertEqual(resp.headers["After-Response"], 'OK') # Test CORS self.assertIn("Access-Control-Allow-Origin", resp.headers) self.assertEqual(resp.headers["Access-Control-Allow-Origin"], '*') # the 'time' value was set in the endpoint handler self.assertIn('time', resp.json_body) # the returned time value should match the resource defined # in tests.example_app.api.resources.py returned_time = resp.json_body['time'] # the endpoint specifies `fields=DateTimeResource._all_fields()` # so, we should expect to find all of them expected_fields = DateTimeResource._all_fields() self.assertExpectedFields(returned_time, expected_fields) self.assertEqual(returned_time['eurodate'], now.strftime("%d.%m.%Y")) def test_successful_post_with_required_params(self): # month is required in the endpoint definition, so we must pass # it in here resp = self.app.post('/api/time/parse', {'month': 2}) self.assertEqual(resp.status_code, 200) self.assertIn('time', resp.json_body) returned_time = resp.json_body['time'] # we didn't specify any other fields in the endpoint definition, # so this one should only get the defaults expected_fields = DateTimeResource._default_fields self.assertExpectedFields(returned_time, expected_fields) self.assertIn('Cache-Control', resp.headers) self.assertEqual('max-age=3', resp.headers['Cache-Control']) def test_successful_json_post_with_required_params(self): # this is the same as the above post, but passes json in the # request body, instead of x-www-form-urlencoded resp = self.app.post_json('/api/time/parse', {'month': 2}) self.assertEqual(resp.status_code, 200) self.assertIn('time', resp.json_body) returned_time = resp.json_body['time'] # we didn't specify any other fields in the endpoint definition, # so this one should only get the defaults expected_fields = DateTimeResource._default_fields self.assertExpectedFields(returned_time, expected_fields) def test_unsuccessful_post_missing_required_params(self): resp = self.app.post('/api/time/parse', status=422) self.assertIn('error', resp.json_body) def test_getting_with_nested_resources(self): test_duration = 60 * 1000 # one minute in milliseconds resp = self.app.get('/api/time/range', {'duration': test_duration}) self.assertEqual(resp.status_code, 200) self.assertIn('range', resp.json_body) returned_range = resp.json_body['range'] self.assertEqual(returned_range['duration_microseconds'], test_duration * 1000) # start has default fields start = returned_range['start'] expected_fields = DateTimeResource._default_fields self.assertExpectedFields(start, expected_fields) # end has all of them end = returned_range['end'] expected_fields = DateTimeResource._all_fields() self.assertExpectedFields(end, expected_fields) def test_resource(self): # Start by resetting the resource. # (multiple test runs from the same process will fail otherwise) resp = self.app.post('/api/resource/reset') self.assertEqual(resp.status_code, 200) self.assertEqual(resp.content_type, 'application/json') self.assertEqual(resp.json, {'key': 'value'}) # Test creating a new resource resp = self.app.put_json('/api/resource', {'key': 'boop'}, headers={'Content-Type': 'application/json'}) self.assertEqual(resp.status_code, 200) self.assertEqual(resp.content_type, 'application/json') self.assertEqual(resp.json, {'key': 'boop'}) # Test retrieving the resource. resp = self.app.get('/api/resource') self.assertEqual(resp.status_code, 200) self.assertEqual(resp.content_type, 'application/json') self.assertEqual(resp.json, {'key': 'boop'}) # Test patching the resource. # Without the correct Content-Type, we expect a 415 error. self.assertRaises(AppError, self.app.patch_json, '/api/resource', {'key': 'value2'}) resp = self.app.patch_json( '/api/resource', {'key': 'value2'}, headers={'Content-Type': 'application/merge-patch+json'}) self.assertEqual(resp.status_code, 200) self.assertEqual(resp.content_type, 'application/json') self.assertEqual(resp.json, {'key': 'value2'}) # Test get to ensure the resource persists. resp = self.app.get('/api/resource') self.assertEqual(resp.status_code, 200) self.assertEqual(resp.content_type, 'application/json') self.assertEqual(resp.json, {'key': 'value2'}) # A NoContentResource shouldn't have a Content-Type header (no content!) resp = self.app.post('/api/blank') self.assertEqual(resp.status_code, 200) self.assertEqual(resp.content_type, None)
class Client(object): def __init__(self): self.core = Core(db_type="memory").execute_wsgi() self.test_core = TestApp(self.core) def migrate_in_memory(self, migrations_path, alembic_ini_path=None, connection=None, revision="head"): config = Config(alembic_ini_path) config.set_main_option("script_location", migrations_path) if connection is not None: config.attributes["connection"] = connection command.upgrade(config, revision) def get_api(self, api_url, *auth): response = APIResponse() __api_url = str(api_url) if auth: self.test_core.set_authorization(auth) test_core_response = self.test_core.get(__api_url) response.json = test_core_response.json response.status = test_core_response.status response.status_code = test_core_response.status_code response.body = test_core_response.body response.content_type = test_core_response.content_type return response def post_api(self, api_url, data, *auth): response = APIResponse() __api_url = str(api_url) if auth: self.test_core.set_authorization(auth) test_core_response = self.test_core.post_json(__api_url, params=data) response.json = json.dumps(test_core_response.json) response.status = test_core_response.status response.status_code = test_core_response.status_code response.body = test_core_response.body return response def patch_api(self, api_url, data, *auth): response = APIResponse() __api_url = str(api_url) if auth: self.test_core.set_authorization(auth) test_core_response = self.test_core.patch_json(__api_url, params=data) response.json = json.dumps(test_core_response.json) response.status = test_core_response.status response.status_code = test_core_response.status_code response.body = test_core_response.body return response def put_api(self, api_url, data, *auth): response = APIResponse() __api_url = str(api_url) if auth: self.test_core.set_authorization(auth) test_core_response = self.test_core.put_json(__api_url, params=data) response.json = json.dumps(test_core_response.json) response.status = test_core_response.status response.status_code = test_core_response.status_code response.body = test_core_response.body return response def delete_api(self, api_url, data, *auth): response = APIResponse() __api_url = str(api_url) if auth: self.test_core.set_authorization(auth) test_core_response = self.test_core.delete_json(__api_url, params=data) response.json = json.dumps(test_core_response.json) response.status = test_core_response.status response.status_code = test_core_response.status_code response.body = test_core_response.body return response
class FunctionalTests(unittest.TestCase): def setUp(self): import tempfile import os.path from . import main self.tmpdir = tempfile.mkdtemp() dbpath = os.path.join(self.tmpdir, "test.db") uri = "file://" + dbpath settings = { "zodbconn.uri": uri, "pyramid.includes": ["pyramid_zodbconn", "pyramid_tm"], } app = main({}, **settings) self.db = app.registry._zodb_databases[""] from webtest import TestApp self.testapp = TestApp(app) def tearDown(self): import shutil self.db.close() shutil.rmtree(self.tmpdir) def test_root(self): res = self.testapp.get("/", status=200) self.assertTrue(b"" in res.body) def test_add_container(self): res = self.testapp.post_json("/", {"app_id": "my_app"}) res = self.testapp.get("/", status=200) self.assertEqual(["my_app"], res.json_body) with pytest.raises(AppError): self.testapp.post_json("/", {"app_idd": "my_app"}) res = self.testapp.get("/my_app", status=200) res = self.testapp.post_json("/my_app", {"container_id": "iasmartweb"}) res = self.testapp.get("/my_app", status=200) self.assertEqual(["iasmartweb"], res.json_body) def test_add_content(self): res = self.testapp.post_json("/", {"app_id": "my_app"}) res = self.testapp.get("/", status=200) self.assertEqual(["my_app"], res.json_body) with pytest.raises(AppError): self.testapp.post_json("/", {"app_idd": "my_app"}) res = self.testapp.get("/my_app", status=200) res = self.testapp.post_json("/my_app", {"container_id": "iasmartweb"}) res = self.testapp.get("/my_app", status=200) self.assertEqual(["iasmartweb"], res.json_body) res = self.testapp.post_json("/my_app/iasmartweb", {"content_id": "bsuttor"}) res = self.testapp.get("/my_app/iasmartweb", status=200) self.assertEqual(["bsuttor"], res.json_body) def test_remove_content(self): res = self.testapp.post_json("/", {"app_id": "my_app"}) res = self.testapp.post_json("/my_app", {"container_id": "imio"}) res = self.testapp.post_json("/my_app/imio", {"container_id": "iasmartweb"}) res = self.testapp.post_json( "/my_app/imio/iasmartweb", { "content_id": "bsuttor", "username": "******", "email": "*****@*****.**", "fullname": "Benoît Suttor", }, ) res = self.testapp.get("/my_app/imio/iasmartweb/bsuttor", status=200) self.assertEqual(res.json.get("email"), "*****@*****.**") res = self.testapp.get("/my_app/imio/iasmartweb", status=200) self.assertEqual(len(res.json), 1) self.testapp.delete_json("/my_app/imio/iasmartweb/bsuttor") res = self.testapp.get("/my_app/imio/iasmartweb", status=200) self.assertEqual(len(res.json), 0) def test_update_content(self): res = self.testapp.post_json("/", {"app_id": "my_app"}) res = self.testapp.post_json("/my_app", {"container_id": "imio"}) res = self.testapp.post_json("/my_app/imio", {"container_id": "iasmartweb"}) res = self.testapp.post_json( "/my_app/imio/iasmartweb", { "content_id": "bsuttor", "username": "******", "email": "*****@*****.**", "fullname": "Benoît Suttor", }, ) res = self.testapp.get("/my_app/imio/iasmartweb", status=200) self.assertEqual(len(res.json), 1) res = self.testapp.patch_json( "/my_app/imio/iasmartweb/bsuttor", {"email": "*****@*****.**"} ) res = self.testapp.get("/my_app/imio/iasmartweb", status=200) self.assertEqual(len(res.json), 1) res = self.testapp.get("/my_app/imio/iasmartweb/bsuttor", status=200) self.assertEqual(res.json.get("email"), "*****@*****.**") def test_replace_content(self): res = self.testapp.post_json("/", {"app_id": "my_app"}) res = self.testapp.post_json("/my_app", {"container_id": "imio"}) res = self.testapp.post_json("/my_app/imio", {"container_id": "iasmartweb"}) res = self.testapp.post_json( "/my_app/imio/iasmartweb", { "content_id": "bsuttor", "username": "******", "email": "*****@*****.**", "fullname": "Benoît Suttor", }, ) res = self.testapp.get("/my_app/imio/iasmartweb", status=200) self.assertEqual(len(res.json), 1) res = self.testapp.put_json( "/my_app/imio/iasmartweb/bsuttor", { "content_id": "benoit", "username": "******", "email": "*****@*****.**", "fullname": "Ben Suttor", }, ) res = self.testapp.get("/my_app/imio/iasmartweb", status=200) self.assertEqual(len(res.json), 1) res = self.testapp.get("/my_app/imio/iasmartweb/bsuttor", status=404) self.assertEqual( res.body, b"The path my_app/imio/iasmartweb/bsuttor is not found" ) # noqa res = self.testapp.get("/my_app/imio/iasmartweb/benoit", status=200) self.assertEqual(res.json.get("email"), "*****@*****.**") res = self.testapp.get("/my_app/imio/iasmartweb", status=200) self.assertEqual(len(res.json), 1) def test_export_csv(self): res = self.testapp.post_json("/", {"app_id": "my_app"}) res = self.testapp.post_json("/my_app", {"container_id": "imio"}) res = self.testapp.post_json("/my_app/imio", {"container_id": "iasmartweb"}) res = self.testapp.post_json("/my_app/imio", {"container_id": "iaurban"}) res = self.testapp.post_json("/my_app/imio", {"container_id": "iateleservice"}) res = self.testapp.post_json( "/my_app/imio/iasmartweb", { "content_id": "bsuttor", "username": "******", "email": "*****@*****.**", "fullname": "Benoît Suttor", "password": "******", }, ) res = self.testapp.post_json( "/my_app/imio/iasmartweb", { "content_id": "jbond", "username": "******", "email": "*****@*****.**", "fullname": "James Bond", "password": "******", }, ) res = self.testapp.post_json( "/my_app/imio/iaurban", { "content_id": "bsuttor", "username": "******", "email": "*****@*****.**", "fullname": "", "password": "******", }, ) res = self.testapp.post_json( "/my_app/imio/iateleservice", { "content_id": "suttorb", "username": "******", "email": "*****@*****.**", "fullname": "benoît suttor", "password": "******", }, ) res = self.testapp.get("/my_app/imio/csv", status=200) self.assertEqual(res.content_type, "text/csv") self.assertTrue("bsuttor" in str(res.body)) self.assertEqual(len(res.body.decode("utf8").split("\r\n")), 4) def test_export_json(self): res = self.testapp.post_json("/", {"app_id": "my_app"}) res = self.testapp.post_json("/my_app", {"container_id": "imio"}) res = self.testapp.post_json("/my_app/imio", {"container_id": "iasmartweb"}) res = self.testapp.post_json("/my_app/imio", {"container_id": "iaurban"}) res = self.testapp.post_json( "/my_app/imio/iasmartweb", { "content_id": "bsuttor", "username": "******", "email": "*****@*****.**", "fullname": "Benoît Suttor", "password": "******", }, ) res = self.testapp.post_json( "/my_app/imio/iasmartweb", { "content_id": "jbond", "username": "******", "email": "*****@*****.**", "fullname": "James Bond", "password": "******", }, ) res = self.testapp.post_json( "/my_app/imio/iaurban", { "content_id": "bsuttor", "username": "******", "email": "*****@*****.**", "fullname": "", "password": "******", }, ) res = self.testapp.get("/my_app/imio/json", status=200) self.assertEqual(res.content_type, "application/json") self.assertEqual(len(res.json_body["users"]), 3)
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 FlaskAdapterTests(unittest.TestCase): def setUp(self): from tests.example_app.flask_app import create_pale_flask_app self.flask_app = create_pale_flask_app() self.app = TestApp(self.flask_app) def assertExpectedFields(self, returned_dict, expected_fields): d = returned_dict.copy() # don't clobber the input for f in expected_fields: self.assertIn(f, d) val = d.pop(f) # don't check the val for now # make sure there's nothing extraneous left in the dict self.assertEqual(len(d.keys()), 0) return def test_successful_get_with_route_args(self): now = datetime.datetime.utcnow() resp = self.app.get('/api/arg_test/arg-a/arg-b') self.assertEqual(resp.status_code, 200) self.assertEqual(resp.json, { "arg_a": "arg-a", "arg_b": "arg-b", }) def test_successful_get_without_params(self): now = datetime.datetime.utcnow() resp = self.app.get('/api/time/current') self.assertEqual(resp.status_code, 200) # Test _after_response_handlers self.assertIn("After-Response", resp.headers) self.assertEqual(resp.headers["After-Response"], 'OK') # Test CORS self.assertIn("Access-Control-Allow-Origin", resp.headers) self.assertEqual(resp.headers["Access-Control-Allow-Origin"], '*') # the 'time' value was set in the endpoint handler self.assertIn('time', resp.json_body) # the returned time value should match the resource defined # in tests.example_app.api.resources.py returned_time = resp.json_body['time'] # the endpoint specifies `fields=DateTimeResource._all_fields()` # so, we should expect to find all of them expected_fields = DateTimeResource._all_fields() self.assertExpectedFields(returned_time, expected_fields) self.assertEqual(returned_time['eurodate'], now.strftime("%d.%m.%Y")) def test_successful_post_with_required_params(self): # month is required in the endpoint definition, so we must pass # it in here resp = self.app.post('/api/time/parse', {'month': 2}) self.assertEqual(resp.status_code, 200) self.assertIn('time', resp.json_body) returned_time = resp.json_body['time'] # we didn't specify any other fields in the endpoint definition, # so this one should only get the defaults expected_fields = DateTimeResource._default_fields self.assertExpectedFields(returned_time, expected_fields) self.assertIn('Cache-Control', resp.headers) self.assertEqual('max-age=3', resp.headers['Cache-Control']) def test_successful_json_post_with_required_params(self): # this is the same as the above post, but passes json in the # request body, instead of x-www-form-urlencoded resp = self.app.post_json('/api/time/parse', {'month': 2}) self.assertEqual(resp.status_code, 200) self.assertIn('time', resp.json_body) returned_time = resp.json_body['time'] # we didn't specify any other fields in the endpoint definition, # so this one should only get the defaults expected_fields = DateTimeResource._default_fields self.assertExpectedFields(returned_time, expected_fields) def test_unsuccessful_post_missing_required_params(self): resp = self.app.post('/api/time/parse', status=422) self.assertIn('error', resp.json_body) def test_getting_with_nested_resources(self): test_duration = 60 * 1000 # one minute in milliseconds resp = self.app.get('/api/time/range', {'duration': test_duration}) self.assertEqual(resp.status_code, 200) self.assertIn('range', resp.json_body) returned_range = resp.json_body['range'] self.assertEqual(returned_range['duration_microseconds'], test_duration * 1000) # start has default fields start = returned_range['start'] expected_fields = DateTimeResource._default_fields self.assertExpectedFields(start, expected_fields) # end has all of them end = returned_range['end'] expected_fields = DateTimeResource._all_fields() self.assertExpectedFields(end, expected_fields) def test_resource(self): # Start by resetting the resource. # (multiple test runs from the same process will fail otherwise) resp = self.app.post('/api/resource/reset') self.assertEqual(resp.status_code, 200) self.assertEqual(resp.content_type, 'application/json') self.assertEqual(resp.json, {'key': 'value'}) # Test creating a new resource resp = self.app.put_json('/api/resource', {'key': 'boop'}, headers={'Content-Type': 'application/json'}) self.assertEqual(resp.status_code, 200) self.assertEqual(resp.content_type, 'application/json') self.assertEqual(resp.json, {'key': 'boop'}) # Test retrieving the resource. resp = self.app.get('/api/resource') self.assertEqual(resp.status_code, 200) self.assertEqual(resp.content_type, 'application/json') self.assertEqual(resp.json, {'key': 'boop'}) # Test patching the resource. # Without the correct Content-Type, we expect a 415 error. self.assertRaises(AppError, self.app.patch_json, '/api/resource', {'key': 'value2'}) resp = self.app.patch_json('/api/resource', {'key': 'value2'}, headers={'Content-Type': 'application/merge-patch+json'}) self.assertEqual(resp.status_code, 200) self.assertEqual(resp.content_type, 'application/json') self.assertEqual(resp.json, {'key': 'value2'}) # Test get to ensure the resource persists. resp = self.app.get('/api/resource') self.assertEqual(resp.status_code, 200) self.assertEqual(resp.content_type, 'application/json') self.assertEqual(resp.json, {'key': 'value2'}) # A NoContentResource shouldn't have a Content-Type header (no content!) resp = self.app.post('/api/blank') self.assertEqual(resp.status_code, 204) self.assertEqual(resp.content_type, None)
class PyramidPeeweeMarshmallowTestCase(unittest.TestCase): def setUp(self): from webtest import TestApp # Imported here in order to avoid dummy warning from pytest db.drop_tables([User]) db.create_tables([User]) self.app = create_app() self.client = TestApp(self.app) def test_list_users(self): _create_user(first_name='Filipe', last_name='Waitman') response = self.client.get('/api/users/') assert response.status_code == 200 assert response.json_body['count'] == 1 assert response.json_body['next_page'] is None assert response.json_body['prev_page'] is None assert 'id' in response.json_body['results'][0] assert 'created' in response.json_body['results'][0] assert response.json_body['results'][0]['first_name'] == 'Filipe' assert response.json_body['results'][0]['last_name'] == 'Waitman' def test_create(self): data = { 'first_name': 'Filipe', 'last_name': 'Waitman', } response = self.client.post_json('/api/users/', data) assert response.status_code == 201 assert response.json_body['first_name'] == 'Filipe' assert response.json_body['last_name'] == 'Waitman' assert response.json_body.get('id') assert response.json_body.get('created') assert User.select().filter(id=response.json_body['id']).count() == 1 instance = User.select().filter(id=response.json_body['id']).get() assert instance.first_name == 'Filipe' assert instance.last_name == 'Waitman' def test_create_errors(self): data = { 'first_name': 'Filipe', 'last_name': 'Waitman', } response = self.client.post('/api/users/', data, status=400) assert response.status_code == 400 assert response.json_body == {'first_name': ['Missing data for required field.'], 'last_name': ['Missing data for required field.'], 'status_code': 400} # noqa del data['first_name'] response = self.client.post_json('/api/users/', data, status=400) assert response.status_code == 400 assert response.json_body == {'first_name': ['Missing data for required field.'], 'status_code': 400} def test_retrieve(self): user = _create_user(first_name='Filipe', last_name='Waitman') response = self.client.get('/api/users/{}/'.format(user.id)) assert response.status_code == 200 assert 'id' in response.json_body assert 'created' in response.json_body assert response.json_body['first_name'] == 'Filipe' assert response.json_body['last_name'] == 'Waitman' def test_retrieve_errors(self): response = self.client.get('/api/users/999/', status=404) assert response.status_code == 404 assert response.json_body == {'status_code': 404} def test_update(self): user = _create_user(first_name='Filipe', last_name='Waitman') data = { 'last_name': 'New', } response = self.client.patch_json('/api/users/{}/'.format(user.id), data) assert response.status_code == 200 assert 'id' in response.json_body assert 'created' in response.json_body assert response.json_body['first_name'] == 'Filipe' assert response.json_body['last_name'] == 'New' instance = User.select().filter(id=response.json_body['id']).get() assert instance.last_name == 'New' def test_update_errors(self): user = _create_user(first_name='Filipe', last_name='Waitman') data = { 'last_name': '', } response = self.client.patch_json('/api/users/{}/'.format(user.id), data, status=400) assert response.status_code == 400 assert response.json_body == {'last_name': ['Shorter than minimum length 1.'], 'status_code': 400} instance = User.select().filter(id=user.id).get() assert instance.last_name == 'Waitman' def test_delete(self): user = _create_user(first_name='Filipe', last_name='Waitman') response = self.client.delete('/api/users/{}/'.format(user.id)) assert response.status_code == 204 assert User.select().filter(id=user.id).count() == 0 def test_doublename(self): user = _create_user(first_name='Filipe', last_name='Waitman') response = self.client.get('/api/users/{}/doublename/'.format(user.id)) assert response.status_code == 200 assert response.json_body == {'doubled': 'FilipeFilipe'} assert response.headers['header-passed-in'] == '1' def test_pagination(self): _create_user(first_name='Filipe', last_name='Waitman') _create_user(first_name='John', last_name='Doe') response = self.client.get('/api/users/') assert response.status_code == 200 assert response.json_body['count'] == 2 assert response.json_body['next_page'] is None assert response.json_body['prev_page'] is None assert len(response.json_body['results']) == 2 response = self.client.get('/api/users/?per_page=1&x=y') assert response.status_code == 200 assert response.json_body['count'] == 2 assert response.json_body['next_page'] is not None assert response.json_body['prev_page'] is None assert len(response.json_body['results']) == 1 assert 'http' in response.json_body['next_page'] assert 'localhost' in response.json_body['next_page'] assert '/api/users/' in response.json_body['next_page'] assert 'x=y' in response.json_body['next_page'] assert 'per_page=1' in response.json_body['next_page'] assert 'page=2' in response.json_body['next_page'] response = self.client.get('/api/users/?per_page=1&page=2') assert response.status_code == 200 assert response.json_body['count'] == 2 assert response.json_body['next_page'] is None assert response.json_body['prev_page'] is not None assert len(response.json_body['results']) == 1 @mock.patch.object(MyBaseAPI, 'get_current_user', return_value=None) def test_permissions(self, *mocks): user = _create_user(first_name='Filipe', last_name='Waitman') # User API response = self.client.get('/api/users/', status=401) assert response.status_code == 401 response = self.client.post('/api/users/', status=401) assert response.status_code == 401 response = self.client.get('/api/users/{}/'.format(user.id), status=401) assert response.status_code == 401 response = self.client.patch('/api/users/{}/'.format(user.id), status=401) assert response.status_code == 401 response = self.client.delete('/api/users/{}/'.format(user.id), status=401) assert response.status_code == 401 response = self.client.get('/api/users/{}/doublename/'.format(user.id), status=401) assert response.status_code == 401 # UserOpen API response = self.client.get('/api/users/{}/doublename_open/'.format(user.id)) assert response.status_code == 200 # UserReadOnly API response = self.client.get('/api/users/read_only/') assert response.status_code == 200 response = self.client.post('/api/users/read_only/', status=403) assert response.status_code == 403 def test_form_data_create(self): data = { 'first_name': 'Filipe', 'last_name': 'Waitman', } response = self.client.post_json('/api/users/form_data/', data, status=400) assert response.status_code == 400 assert response.json_body == {'first_name': ['Missing data for required field.'], 'last_name': ['Missing data for required field.'], 'status_code': 400} # noqa response = self.client.post('/api/users/form_data/', data) assert response.status_code == 201 def test_no_pagination_list(self): _create_user(first_name='Filipe', last_name='Waitman') response = self.client.get('/api/users/no_pagination/') assert response.status_code == 200 assert len(response.json_body) == 1 assert 'next_page' not in response.json_body assert 'prev_page' not in response.json_body assert 'id' in response.json_body[0] assert 'created' in response.json_body[0] assert response.json_body[0]['first_name'] == 'Filipe' assert response.json_body[0]['last_name'] == 'Waitman' def test_no_pagination_list_via_query_params(self): _create_user(first_name='Filipe', last_name='Waitman') response = self.client.get('/api/users/?paginate=f') assert response.status_code == 200 assert len(response.json_body) == 1 assert 'next_page' not in response.json_body assert 'prev_page' not in response.json_body assert 'id' in response.json_body[0] assert 'created' in response.json_body[0] assert response.json_body[0]['first_name'] == 'Filipe' assert response.json_body[0]['last_name'] == 'Waitman' def test_exception_behavior(self): response = self.client.get('/api/users/exception/handled/', status=499) assert response.status_code == 499 assert response.json_body == {'detail': 'Now this is a weird HTTP code', 'status_code': 499} with pytest.raises(ZeroDivisionError): self.client.get('/api/users/exception/unhandled/') response = self.client.get('/api/users', status=404) # Missing trailing slash (out of wrf scope) assert response.status_code == 404
class Client(object): def __init__(self): self.test_core = TestApp(Core().execute_wsgi()) def get_api(self, api_url, *auth): response = APIResponse() __api_url = str(api_url) if auth: self.test_core.set_authorization(auth) test_core_response = self.test_core.get(__api_url) response.json = test_core_response.json response.status = test_core_response.status response.status_code = test_core_response.status_code response.body = test_core_response.body response.content_type = test_core_response.content_type return response def post_api(self, api_url, data, *auth): response = APIResponse() __api_url = str(api_url) if auth: self.test_core.set_authorization(auth) test_core_response = self.test_core.post_json(__api_url, params=data) response.json = json.dumps(test_core_response.json) response.status = test_core_response.status response.status_code = test_core_response.status_code response.body = test_core_response.body return response def patch_api(self, api_url, data, *auth): response = APIResponse() __api_url = str(api_url) if auth: self.test_core.set_authorization(auth) test_core_response = self.test_core.patch_json(__api_url, params=data) response.json = json.dumps(test_core_response.json) response.status = test_core_response.status response.status_code = test_core_response.status_code response.body = test_core_response.body return response def put_api(self, api_url, data, *auth): response = APIResponse() __api_url = str(api_url) if auth: self.test_core.set_authorization(auth) test_core_response = self.test_core.put_json(__api_url, params=data) response.json = json.dumps(test_core_response.json) response.status = test_core_response.status response.status_code = test_core_response.status_code response.body = test_core_response.body return response def delete_api(self, api_url, data, *auth): response = APIResponse() __api_url = str(api_url) if auth: self.test_core.set_authorization(auth) test_core_response = self.test_core.delete_json(__api_url, params=data) response.json = json.dumps(test_core_response.json) response.status = test_core_response.status response.status_code = test_core_response.status_code response.body = test_core_response.body return response