diff --git a/Makefile.local b/Makefile.local index a9e60968..6b3324de 100644 --- a/Makefile.local +++ b/Makefile.local @@ -1,13 +1,13 @@ SWH_WEB_UI=./bin/swh-web-ui-dev FLAG=-v NOSEFLAGS=-v -s TOOL=pandoc run-dev: $(SWH_WEB_UI) $(FLAG) --config ./resources/test/webapp.yml run: # works with the default ~/.config/swh/webapp.yml file $(SWH_WEB_UI) $(FLAG) doc: - cd swh/web/ui/templates/includes/ && pandoc -o api-header.html api-header.md + cd swh/web/ui/templates/includes/ && pandoc -o apidoc-header.html apidoc-header.md diff --git a/swh/web/ui/templates/api-endpoints.html b/swh/web/ui/templates/api-endpoints.html new file mode 100644 index 00000000..ed90f5d4 --- /dev/null +++ b/swh/web/ui/templates/api-endpoints.html @@ -0,0 +1,42 @@ +{% extends "layout.html" %} +{% block title %}API Overview{% endblock %} +{% block content %} + +
+

This lists the current API endpoints for version 1.

+ +

+ For a more general description, please refer + to the main documentation. +

+ +

+ The currently opened endpoints are blue. You can follow them for a + more detailed description. The other endpoints are upcoming and are + not followable for now. +

+

+

+

+
+ +
+ + {% for route, doc in doc_routes %} + {% if 'tags' in doc and doc['tags'] is not none %} +
+

{{ route }}

+ ({{ ', '.join(doc['tags']) }}) + {% else %} +
+

{{ route }}

+ {% endif %} + {% autoescape off %}{{ doc['docstring'] | safe_docstring_display }}{% endautoescape %} +
+ {% endfor %} +
+{% endblock %} diff --git a/swh/web/ui/templates/api.html b/swh/web/ui/templates/api.html index c650e47e..12236a5c 100644 --- a/swh/web/ui/templates/api.html +++ b/swh/web/ui/templates/api.html @@ -1,21 +1,8 @@ {% extends "layout.html" %} -{% block title %}API Overview{% endblock %} +{% block title %}API Documentation{% endblock %} {% block content %}
- {% include 'includes/api-header.html' %} - {% for route, doc in doc_routes %} - {% if 'tags' in doc and doc['tags'] is not none %} -
-

{{ route }}

- ({{ ', '.join(doc['tags']) }}) - {% else %} -
-

{{ route }}

- {% endif %} - {% autoescape off %}{{ doc['docstring'] | safe_docstring_display }}{% endautoescape %} -
-
- {% endfor %} + {% include 'includes/apidoc-header.html' %}
{% endblock %} diff --git a/swh/web/ui/templates/includes/api-header.html b/swh/web/ui/templates/includes/apidoc-header.html similarity index 93% rename from swh/web/ui/templates/includes/api-header.html rename to swh/web/ui/templates/includes/apidoc-header.html index 2ca3de37..ae60032b 100644 --- a/swh/web/ui/templates/includes/api-header.html +++ b/swh/web/ui/templates/includes/apidoc-header.html @@ -1,178 +1,176 @@ -

Welcome to Software Heritage project's API.

+

Welcome to Software Heritage project's API documentation.

Table of Contents

Version

-

Current version is 1.

+

Current version is 1.

Schema

-

Api access is over https and accessed through https://archive.softwareheritage.org/api/1/.

-

Data is sent and received as json.

+

Api access is over https and accessed through https://archive.softwareheritage.org/api/1/.

+

Data is sent and received as json by default.

Example:

$ curl -i https://archive.softwareheritage.org/api/1/stat/counters/
 HTTP/1.1 200 OK
 Date: Mon, 16 Jan 2017 10:57:56 GMT
 Server: Apache
 Content-Type: application/json
 Content-Length: 395
 Vary: Accept-Encoding
 Access-Control-Allow-Origin: *
 Connection: close
 
 {
   "directory_entry_rev": 3039473,
   "person": 13903080,
   "entity": 7103795,
   "skipped_content": 17864,
   "entity_history": 7147753,
   "revision_history": 720840448,
   "revision": 703277184,
   "directory": 2616883200,
   "release": 5692900,
   "origin": 49938216,
   "directory_entry_dir": 2140887552,
   "occurrence_history": 254274832,
   "occurrence": 241899344,
   "content": 3155739136,
   "directory_entry_file": 3173807104
 }

Mimetype override

The response output can be sent as yaml provided the client specifies it using the header field.

For example:

curl -i -H 'Accept: application/yaml' https://archive.softwareheritage.org/api/1/stat/counters/
 HTTP/1.1 200 OK
 Date: Mon, 16 Jan 2017 12:31:50 GMT
 Server: Apache
 Content-Type: application/yaml
 Content-Length: 372
 Access-Control-Allow-Origin: *
 Connection: close
 
 {content: 3155758336,
  directory: 2616955136,
  directory_entry_dir: 2140925824,
  directory_entry_file: 3173833984,
  directory_entry_rev: 3039473,
  entity: 7103741,
  entity_history: 7148121,
  occurrence: 241887488,
  occurrence_history: 254277584,
  origin: 49939848,
  person: 13898394,
  release: 5693922,
  revision: 703275840,
  revision_history: 720842176,
  skipped_content: 17864}

Parameters

Some API endpoints can be used with with local parameters. The url then needs to be adapted accordingly.

For example:

https://archive.softwareheritage.org/api/1/<endpoint-name>?<field0>=<value0>&<field1>=<value1>

where:

Global parameter

One parameter is defined for all api endpoints fields. It permits to filter the output fields per key.

For example, to only list the number of contents, revisions, directories on the statistical endpoints, one uses:

$ curl https://archive.softwareheritage.org/api/1/stat/counters/\?fields\=content,directory,revision | jq
 {
   "content": 3155739136,
   "revision": 703277184,
   "directory": 2616883200
 }

Note: If the keys provided to filter on do not exist, they are ignored.

Client errors

There are 2 kinds of error.

In that case, the http error code will reflect that error and a json response is sent with the detailed error.

Bad request

This means that the input is incorrect.

Example:

curl -i https://archive.softwareheritage.org/api/1/content/1/
 HTTP/1.1 400 BAD REQUEST
 Date: Mon, 16 Jan 2017 11:28:08 GMT
 Server: Apache
 Content-Type: application/json
 Content-Length: 44
 Connection: close
 
 {"error": "Invalid checksum query string 1"}

Here, the api content expects an hash identifier.

Not found

This means that the request is ok but we do not found the information the user requests.

Example:

curl -i https://archive.softwareheritage.org/api/1/content/04740277a81c5be6c16f6c9da488ca073b770d7f/
 HTTP/1.1 404 NOT FOUND
 Date: Mon, 16 Jan 2017 11:31:46 GMT
 Server: Apache
 Content-Type: application/json
 Content-Length: 77
 Connection: close
 
 {"error": "Content with 04740277a81c5be6c16f6c9da488ca073b770d7f not found."}

Terminology

You will find below the terminology the project swh uses. More details can be found on swh's wiki glossary page.

Content

A (specific version of a) file stored in the archive, identified by its cryptographic hashes (SHA1, "git-like" SHA1, SHA256) and its size.

Also known as: Blob Note.

(Cryptographic) hash

A fixed-size "summary" of a stream of bytes that is easy to compute, and hard to reverse.

Also known as: Checksum, Digest.

Directory

A set of named pointers to contents (file entries), directories (directory entries) and revisions (revision entries).

Origin

A location from which a coherent set of sources has been obtained.

Also known as: Data source.

Examples:

Project

An organized effort to develop a software product.

Projects might be nested following organizational structures (sub-project, sub-sub-project), are associated to a number of human-meaningful metadata, and release software products via Origins.

Release

A revision that has been marked by a project as noteworthy with a specific, usually mnemonic, name (for instance, a version number).

Also known as: Tag (Git-specific terminology).

Examples:

Revision

A "point in time" snapshot in the development history of a project.

Also known as: Commit

Examples:

Opened endpoints

-

(may have to move to /api/1/)

-

Below, you will find the opened endpoints. This will permit you to read information on the origin we load up until now.

+

Open api endpoints is accessed at https://archive.softwareheritage.org/api/1/.

diff --git a/swh/web/ui/templates/includes/api-header.md b/swh/web/ui/templates/includes/apidoc-header.md similarity index 85% rename from swh/web/ui/templates/includes/api-header.md rename to swh/web/ui/templates/includes/apidoc-header.md index 7f89a662..5b7e62a5 100644 --- a/swh/web/ui/templates/includes/api-header.md +++ b/swh/web/ui/templates/includes/apidoc-header.md @@ -1,255 +1,253 @@ -Welcome to Software Heritage project's API. +Welcome to Software Heritage project's API documentation. **Table of Contents** +- [Version](#version) - [Schema](#schema) - - [Mimetype override](#mimetype-override) - - [Parameters](#parameters) - - [Global parameter](#global-parameter) - - [Client errors](#client-errors) - - [Bad request](#bad-request) - - [Not found](#not-found) - - [Terminology](#terminology) - - [Content](#content) - - [(Cryptographic) hash](#cryptographic-hash) - - [Directory](#directory) - - [Origin](#origin) - - [Project](#project) - - [Release](#release) - - [Revision](#revision) - - [Opened endpoints](#opened-endpoints) +- [Mimetype override](#mimetype-override) +- [Parameters](#parameters) + - [Global parameter](#global-parameter) +- [Client errors](#client-errors) + - [Bad request](#bad-request) + - [Not found](#not-found) +- [Terminology](#terminology) + - [Content](#content) + - [(Cryptographic) hash](#cryptographic-hash) + - [Directory](#directory) + - [Origin](#origin) + - [Project](#project) + - [Release](#release) + - [Revision](#revision) +- [Opened endpoints](#opened-endpoints) ### Version -Current version is 1. +Current version is [1](/api/1/). ### Schema -Api access is over https and accessed through [https://archive.softwareheritage.org/api/1/](https://archive.softwareheritage.org/api/). +Api access is over https and accessed through [https://archive.softwareheritage.org/api/1/](/api/1/). -Data is sent and received as json. +Data is sent and received as json by default. Example: ``` shell $ curl -i https://archive.softwareheritage.org/api/1/stat/counters/ HTTP/1.1 200 OK Date: Mon, 16 Jan 2017 10:57:56 GMT Server: Apache Content-Type: application/json Content-Length: 395 Vary: Accept-Encoding Access-Control-Allow-Origin: * Connection: close { "directory_entry_rev": 3039473, "person": 13903080, "entity": 7103795, "skipped_content": 17864, "entity_history": 7147753, "revision_history": 720840448, "revision": 703277184, "directory": 2616883200, "release": 5692900, "origin": 49938216, "directory_entry_dir": 2140887552, "occurrence_history": 254274832, "occurrence": 241899344, "content": 3155739136, "directory_entry_file": 3173807104 } ``` #### Mimetype override The response output can be sent as yaml provided the client specifies it using the header field. For example: ``` shell curl -i -H 'Accept: application/yaml' https://archive.softwareheritage.org/api/1/stat/counters/ HTTP/1.1 200 OK Date: Mon, 16 Jan 2017 12:31:50 GMT Server: Apache Content-Type: application/yaml Content-Length: 372 Access-Control-Allow-Origin: * Connection: close {content: 3155758336, directory: 2616955136, directory_entry_dir: 2140925824, directory_entry_file: 3173833984, directory_entry_rev: 3039473, entity: 7103741, entity_history: 7148121, occurrence: 241887488, occurrence_history: 254277584, origin: 49939848, person: 13898394, release: 5693922, revision: 703275840, revision_history: 720842176, skipped_content: 17864} ``` ### Parameters Some API endpoints can be used with with local parameters. The url then needs to be adapted accordingly. For example: ``` text https://archive.softwareheritage.org/api/1/?=&= ``` where: - field0 is an appropriate field for the and value0 - field1 is an appropriate field for the and value1 #### Global parameter One parameter is defined for all api endpoints `fields`. It permits to filter the output fields per key. For example, to only list the number of contents, revisions, directories on the statistical endpoints, one uses: ``` shell $ curl https://archive.softwareheritage.org/api/1/stat/counters/\?fields\=content,directory,revision | jq { "content": 3155739136, "revision": 703277184, "directory": 2616883200 } ``` Note: If the keys provided to filter on do not exist, they are ignored. ### Client errors There are 2 kinds of error. In that case, the http error code will reflect that error and a json response is sent with the detailed error. #### Bad request This means that the input is incorrect. Example: ``` shell curl -i https://archive.softwareheritage.org/api/1/content/1/ HTTP/1.1 400 BAD REQUEST Date: Mon, 16 Jan 2017 11:28:08 GMT Server: Apache Content-Type: application/json Content-Length: 44 Connection: close {"error": "Invalid checksum query string 1"} ``` Here, the api content expects an hash identifier. #### Not found This means that the request is ok but we do not found the information the user requests. Example: ``` shell curl -i https://archive.softwareheritage.org/api/1/content/04740277a81c5be6c16f6c9da488ca073b770d7f/ HTTP/1.1 404 NOT FOUND Date: Mon, 16 Jan 2017 11:31:46 GMT Server: Apache Content-Type: application/json Content-Length: 77 Connection: close {"error": "Content with 04740277a81c5be6c16f6c9da488ca073b770d7f not found."} ``` ### Terminology You will find below the terminology the project swh uses. More details can be found on [swh's wiki glossary page](https://wiki.softwareheritage.org/index.php?title=Glossary). #### Content A (specific version of a) file stored in the archive, identified by its cryptographic hashes (SHA1, "git-like" SHA1, SHA256) and its size. Also known as: Blob Note. #### (Cryptographic) hash A fixed-size "summary" of a stream of bytes that is easy to compute, and hard to reverse. Also known as: Checksum, Digest. #### Directory A set of named pointers to contents (file entries), directories (directory entries) and revisions (revision entries). #### Origin A location from which a coherent set of sources has been obtained. Also known as: Data source. Examples: - a Git repository - a directory containing tarballs - the history of a Debian package on snapshot.debian.org. #### Project An organized effort to develop a software product. Projects might be nested following organizational structures (sub-project, sub-sub-project), are associated to a number of human-meaningful metadata, and release software products via Origins. #### Release A revision that has been marked by a project as noteworthy with a specific, usually mnemonic, name (for instance, a version number). Also known as: Tag (Git-specific terminology). Examples: - a Git tag with its name - a tarball with its name - a Debian source package with its version number. #### Revision A "point in time" snapshot in the development history of a project. Also known as: Commit Examples: - a Git commit ### Opened endpoints -(may have to move to /api/1/) - -Below, you will find the opened endpoints. This will permit you to -read information on the origin we load up until now. +Open api endpoints is accessed at [https://archive.softwareheritage.org/api/1/](/api/1/). diff --git a/swh/web/ui/tests/views/test_browse.py b/swh/web/ui/tests/views/test_browse.py index 843f5e8f..d1ced636 100644 --- a/swh/web/ui/tests/views/test_browse.py +++ b/swh/web/ui/tests/views/test_browse.py @@ -1,2092 +1,2103 @@ # Copyright (C) 2015 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information from nose.tools import istest from unittest import TestCase from unittest.mock import patch from flask import url_for from swh.web.ui.views import browse from swh.web.ui.exc import BadInputExc, NotFoundExc from .. import test_app class FileMock(): def __init__(self, filename): self.filename = filename class StaticViews(test_app.SWHViewTestCase): render_template = False @patch('swh.web.ui.apidoc.APIUrls') @istest - def browse_api_doc(self, mock_api_urls): + def browse_api_endpoints(self, mock_api_urls): # given endpoints = { '/a/doc/endpoint/': 'relevant documentation', '/some/other/endpoint/': 'more docstrings'} mock_api_urls.apidoc_routes = endpoints # when - rv = self.client.get('/api/') + rv = self.client.get('/api/1/') # then self.assertEquals(rv.status_code, 200) self.assertIsNotNone( self.get_context_variable('doc_routes'), sorted(endpoints.items()) ) + self.assert_template_used('api-endpoints.html') + + @istest + def browse_api_doc(self): + # given + + # when + rv = self.client.get('/api/') + + # then + self.assertEquals(rv.status_code, 200) self.assert_template_used('api.html') @istest def browse_archive(self): # when rv = self.client.get('/browse/') # then self.assertEquals(rv.status_code, 200) self.assert_template_used('browse.html') class SearchRedirectsView(test_app.SWHViewTestCase): render_template = False @istest def search_origin_simple(self): # when rv = self.client.get('/origin/search/?origin_id=1&meaningless_arg=42') # then self.assertRedirects(rv, url_for('browse_origin', origin_id=1)) @istest def search_origin_type_url(self): # when rv = self.client.get('/origin/search/?origin_type=git' '&origin_url=http://cool/project/url' '&meaningless_arg=42') # then self.assertRedirects(rv, url_for('browse_origin', origin_type='git', origin_url='http://cool/project/url')) @istest def search_directory_dir_sha1(self): # when rv = self.client.get('/directory/search/?sha1_git=some_sha1' '&path=some/path/in/folder' '&meaningless_arg=gandalf') # then self.assertRedirects(rv, url_for('browse_directory', sha1_git='some_sha1', path='some/path/in/folder')) @istest def search_directory_dir_sha1_nopath(self): # when rv = self.client.get('/directory/search/?sha1_git=some_sha1' '&meaningless_arg=gandalf') # then self.assertRedirects(rv, url_for('browse_directory', sha1_git='some_sha1')) @istest def search_directory_rev_sha1(self): # when rv = self.client.get('/directory/search/?sha1_git=some_sha1' '&dir_path=some/path/in/folder' '&meaningless_arg=gandalf') # then self.assertRedirects(rv, url_for('browse_revision_directory', sha1_git='some_sha1', dir_path='some/path/in/folder')) @istest def search_directory_rev_sha1_nopath(self): # when rv = self.client.get('/directory/search/?sha1_git=some_sha1' '&dir_path=' '&meaningless_arg=gandalf') # then self.assertRedirects(rv, url_for('browse_revision_directory', sha1_git='some_sha1')) @istest def search_directory_dir_time_place(self): # when rv = self.client.get('/directory/search/?origin_id=42' '&branch_name=refs/heads/tail' '&meaningless_arg=gandalf' '&path=some/path') # then self.assertRedirects(rv, url_for( 'browse_revision_directory_through_origin', origin_id=42, branch_name='refs/heads/tail', path='some/path', ts=None)) @istest def search_revision_sha1(self): # when rv = self.client.get('/revision/search/?sha1_git=some_sha1') # then self.assertRedirects(rv, url_for('browse_revision', sha1_git='some_sha1')) @istest def search_revision_time_place(self): # when rv = self.client.get('/revision/search/?origin_id=42' '&branch_name=big/branch/on/tree' '&ts=meaningful_ts') # then self.assertRedirects(rv, url_for('browse_revision_with_origin', origin_id=42, branch_name='big/branch/on/tree', ts='meaningful_ts')) class SearchSymbolView(test_app.SWHViewTestCase): render_template = False @istest def search_symbol(self): # when rv = self.client.get('/content/symbol/') self.assertEqual(rv.status_code, 200) self.assertEqual(self.get_context_variable('result'), None) self.assertEqual(self.get_context_variable('message'), '') self.assertEqual(self.get_context_variable('linknext'), None) self.assertEqual(self.get_context_variable('linkprev'), None) self.assert_template_used('symbols.html') @patch('swh.web.ui.views.browse.api') @istest def search_symbol_with_result(self, mock_api): # given stub_results = [ { 'kind': 'function', 'name': 'hy', 'sha1': 'some-hash', }, ] mock_api.api_content_symbol.return_value = { 'results': stub_results, } # when rv = self.client.get('/content/symbol/?q=hy') self.assertEqual(rv.status_code, 200) self.assertEqual(self.get_context_variable('result'), stub_results) self.assertEqual(self.get_context_variable('message'), '') self.assertEqual(self.get_context_variable('linknext'), None) self.assertEqual(self.get_context_variable('linkprev'), None) self.assert_template_used('symbols.html') mock_api.api_content_symbol.assert_called_once_with('hy') @patch('swh.web.ui.views.browse.api') @istest def search_symbol_with_result_and_pages(self, mock_api): # given stub_results = [ { 'kind': 'function', 'name': 'hy', 'sha1': 'some-hash', } ] mock_api.api_content_symbol.return_value = { 'results': stub_results, 'headers': { 'link-next': 'some-link', } } # when rv = self.client.get('/content/symbol/?q=hy&per_page=1') self.assertEqual(rv.status_code, 200) self.assertEqual(self.get_context_variable('result'), stub_results) self.assertEqual(self.get_context_variable('message'), '') self.assertEqual( self.get_context_variable('linknext'), '/content/symbol/?q=hy&last_sha1=some-hash&per_page=1') self.assertEqual(self.get_context_variable('linkprev'), None) self.assert_template_used('symbols.html') mock_api.api_content_symbol.assert_called_once_with('hy') @patch('swh.web.ui.views.browse.api') @istest def search_symbol_bad_input(self, mock_api): # given mock_api.api_content_symbol.side_effect = BadInputExc('error msg') # when rv = self.client.get('/content/symbol/?q=hello|hy') self.assertEqual(rv.status_code, 200) self.assertEqual(self.get_context_variable('message'), 'error msg') self.assertEqual(self.get_context_variable('result'), None) self.assertEqual(self.get_context_variable('linknext'), None) self.assertEqual(self.get_context_variable('linkprev'), None) self.assert_template_used('symbols.html') mock_api.api_content_symbol.assert_called_once_with('hello|hy') class SearchView(test_app.SWHViewTestCase): render_template = False @istest def search_default(self): # when rv = self.client.get('/content/search/') self.assertEqual(rv.status_code, 200) self.assertEqual(self.get_context_variable('message'), '') self.assertEqual(self.get_context_variable('search_res'), None) self.assert_template_used('search.html') @patch('swh.web.ui.views.browse.api') @istest def search_get_query_hash_not_found(self, mock_api): # given mock_api.api_search.return_value = { 'search_res': [{ 'filename': None, 'sha1': 'sha1:456', 'found': False}], 'search_stats': {'nbfiles': 1, 'pct': 100}} # when rv = self.client.get('/content/search/?q=sha1:456') self.assertEqual(rv.status_code, 200) self.assertEqual(self.get_context_variable('message'), '') self.assertEqual(self.get_context_variable('search_res'), [ {'filename': None, 'sha1': 'sha1:456', 'found': False}]) self.assert_template_used('search.html') mock_api.api_search.assert_called_once_with('sha1:456') @patch('swh.web.ui.views.browse.api') @istest def search_get_query_hash_bad_input(self, mock_api): # given mock_api.api_search.side_effect = BadInputExc('error msg') # when rv = self.client.get('/content/search/?q=sha1_git:789') self.assertEqual(rv.status_code, 200) self.assertEqual(self.get_context_variable('message'), 'error msg') self.assertEqual(self.get_context_variable('search_res'), None) self.assert_template_used('search.html') mock_api.api_search.assert_called_once_with('sha1_git:789') @patch('swh.web.ui.views.browse.api') @istest def search_get_query_hash_found(self, mock_api): # given mock_api.api_search.return_value = { 'search_res': [{ 'filename': None, 'sha1': 'sha1:123', 'found': True}], 'search_stats': {'nbfiles': 1, 'pct': 100}} # when rv = self.client.get('/content/search/?q=sha1:123') self.assertEqual(rv.status_code, 200) self.assertEqual(self.get_context_variable('message'), '') self.assertEqual(len(self.get_context_variable('search_res')), 1) resp = self.get_context_variable('search_res')[0] self.assertTrue(resp is not None) self.assertEqual(resp['sha1'], 'sha1:123') self.assertEqual(resp['found'], True) self.assert_template_used('search.html') mock_api.api_search.assert_called_once_with('sha1:123') @patch('swh.web.ui.views.browse.request') @patch('swh.web.ui.views.browse.api') @istest def search_post_hashes_bad_input(self, mock_api, mock_request): # given mock_request.form = {'a': ['456caf10e9535160d90e874b45aa426de762f19f'], 'b': ['745bab676c8f3cec8016e0c39ea61cf57e518865']} mock_request.method = 'POST' mock_api.api_search.side_effect = BadInputExc( 'error bad input') # when (mock_request completes the post request) rv = self.client.post('/content/search/') # then self.assertEqual(rv.status_code, 200) self.assertEqual(self.get_context_variable('search_stats'), {'nbfiles': 0, 'pct': 0}) self.assertEqual(self.get_context_variable('search_res'), None) self.assertEqual(self.get_context_variable('message'), 'error bad input') self.assert_template_used('search.html') @patch('swh.web.ui.views.browse.request') @patch('swh.web.ui.views.browse.api') @istest def search_post_hashes_none(self, mock_api, mock_request): # given mock_request.form = {'a': ['456caf10e9535160d90e874b45aa426de762f19f'], 'b': ['745bab676c8f3cec8016e0c39ea61cf57e518865']} mock_request.method = 'POST' mock_api.api_search.return_value = { 'search_stats': {'nbfiles': 2, 'pct': 0}, 'search_res': [{'filename': 'a', 'sha1': '456caf10e9535160d90e874b45aa426de762f19f', 'found': False}, {'filename': 'b', 'sha1': '745bab676c8f3cec8016e0c39ea61cf57e518865', 'found': False}]} # when (mock_request completes the post request) rv = self.client.post('/content/search/') # then self.assertEqual(rv.status_code, 200) self.assertIsNotNone(self.get_context_variable('search_res')) self.assertTrue(self.get_context_variable('search_stats') is not None) self.assertEqual(len(self.get_context_variable('search_res')), 2) stats = self.get_context_variable('search_stats') self.assertEqual(stats['nbfiles'], 2) self.assertEqual(stats['pct'], 0) a, b = self.get_context_variable('search_res') self.assertEqual(a['found'], False) self.assertEqual(b['found'], False) self.assertEqual(self.get_context_variable('message'), '') self.assert_template_used('search.html') @patch('swh.web.ui.views.browse.request') @patch('swh.web.ui.views.browse.api') @istest def search_post_hashes_some(self, mock_api, mock_request): # given mock_request.form = {'a': '456caf10e9535160d90e874b45aa426de762f19f', 'b': '745bab676c8f3cec8016e0c39ea61cf57e518865'} mock_request.method = 'POST' mock_api.api_search.return_value = { 'search_stats': {'nbfiles': 2, 'pct': 50}, 'search_res': [{'filename': 'a', 'sha1': '456caf10e9535160d90e874b45aa426de762f19f', 'found': False}, {'filename': 'b', 'sha1': '745bab676c8f3cec8016e0c39ea61cf57e518865', 'found': True}]} # when (mock_request completes the post request) rv = self.client.post('/content/search/') # then self.assertEqual(rv.status_code, 200) self.assertIsNotNone(self.get_context_variable('search_res')) self.assertEqual(len(self.get_context_variable('search_res')), 2) self.assertTrue(self.get_context_variable('search_stats') is not None) stats = self.get_context_variable('search_stats') self.assertEqual(stats['nbfiles'], 2) self.assertEqual(stats['pct'], 50) self.assertEqual(self.get_context_variable('message'), '') a, b = self.get_context_variable('search_res') self.assertEqual(a['found'], False) self.assertEqual(b['found'], True) self.assert_template_used('search.html') class ContentView(test_app.SWHViewTestCase): render_template = False @patch('swh.web.ui.views.browse.api') @istest def browse_content_ko_not_found(self, mock_api): # given mock_api.api_content_metadata.side_effect = NotFoundExc( 'Not found!') # when rv = self.client.get('/browse/content/sha1:sha1-hash/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('content.html') self.assertEqual(self.get_context_variable('message'), 'Not found!') self.assertIsNone(self.get_context_variable('content')) mock_api.api_content_metadata.assert_called_once_with( 'sha1:sha1-hash') @patch('swh.web.ui.views.browse.api') @istest def browse_content_ko_bad_input(self, mock_api): # given mock_api.api_content_metadata.side_effect = BadInputExc( 'Bad input!') # when rv = self.client.get('/browse/content/sha1:sha1-hash/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('content.html') self.assertEqual(self.get_context_variable('message'), 'Bad input!') self.assertIsNone(self.get_context_variable('content')) mock_api.api_content_metadata.assert_called_once_with( 'sha1:sha1-hash') @patch('swh.web.ui.views.browse.service') @patch('swh.web.ui.views.browse.api') @istest def browse_content(self, mock_api, mock_service): # given stub_content = { 'sha1': 'sha1-hash' } mock_api.api_content_metadata.return_value = stub_content mock_api.api_content_filetype.return_value = { 'mimetype': 'text/plain', } mock_api.api_content_language.return_value = { 'lang': 'Hy', } mock_api.api_content_license.return_value = { 'licenses': ['MIT', 'BSD'], } mock_service.lookup_content_raw.return_value = { 'data': b'blah' } mock_api.api_content_ctags.return_value = [ { 'line': 12, }, { 'line': 14, } ] expected_content = { 'sha1': 'sha1-hash', 'data': 'blah', 'encoding': None, 'mimetype': 'text/plain', 'language': 'Hy', 'licenses': "MIT, BSD", } # when rv = self.client.get('/browse/content/sha1:sha1-hash/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('content.html') self.assertIsNone(self.get_context_variable('message')) actual_content = self.get_context_variable('content') actual_content.pop('ctags') self.assertEqual(actual_content, expected_content) mock_service.lookup_content_raw.assert_called_once_with( 'sha1:sha1-hash') mock_api.api_content_language.assert_called_once_with('sha1:sha1-hash') mock_api.api_content_filetype.assert_called_once_with('sha1:sha1-hash') mock_api.api_content_license.assert_called_once_with('sha1:sha1-hash') mock_api.api_content_metadata.assert_called_once_with('sha1:sha1-hash') mock_api.api_content_ctags.assert_called_once_with('sha1:sha1-hash') @patch('swh.web.ui.views.browse.service') @patch('swh.web.ui.views.browse.api') @istest def browse_content_less_data(self, mock_api, mock_service): # given stub_content = { 'sha1': 'ha1', } mock_api.api_content_metadata.return_value = stub_content mock_api.api_content_filetype.return_value = None mock_api.api_content_language.return_value = None mock_api.api_content_license.return_value = None mock_service.lookup_content_raw.return_value = None mock_api.api_content_ctags.return_value = [] expected_content = { 'sha1': 'ha1', 'data': None, 'encoding': None, 'mimetype': None, 'language': None, 'licenses': None, 'ctags': None, } # when rv = self.client.get('/browse/content/sha1:ha1/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('content.html') self.assertIsNone(self.get_context_variable('message')) actual_content = self.get_context_variable('content') self.assertEqual(actual_content, expected_content) mock_service.lookup_content_raw.assert_called_once_with('sha1:ha1') mock_api.api_content_language.assert_called_once_with('sha1:ha1') mock_api.api_content_filetype.assert_called_once_with('sha1:ha1') mock_api.api_content_license.assert_called_once_with('sha1:ha1') mock_api.api_content_metadata.assert_called_once_with('sha1:ha1') mock_api.api_content_ctags.assert_called_once_with('sha1:ha1') @patch('swh.web.ui.views.browse.redirect') @patch('swh.web.ui.views.browse.url_for') @istest def browse_content_raw(self, mock_urlfor, mock_redirect): # given stub_content_raw = b'some-data' mock_urlfor.return_value = '/api/content/sha1:sha1-hash/raw/' mock_redirect.return_value = stub_content_raw # when rv = self.client.get('/browse/content/sha1:sha1-hash/raw/') self.assertEqual(rv.status_code, 200) self.assertEqual(rv.data, stub_content_raw) mock_urlfor.assert_called_once_with('api_content_raw', q='sha1:sha1-hash') mock_redirect.assert_called_once_with( '/api/content/sha1:sha1-hash/raw/') class DirectoryView(test_app.SWHViewTestCase): render_template = False @patch('swh.web.ui.views.browse.api') @istest def browse_directory_ko_bad_input(self, mock_api): # given mock_api.api_directory.side_effect = BadInputExc( 'Invalid hash') # when rv = self.client.get('/browse/directory/sha2-invalid/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('directory.html') self.assertEqual(self.get_context_variable('message'), 'Invalid hash') self.assertEqual(self.get_context_variable('files'), []) mock_api.api_directory.assert_called_once_with( 'sha2-invalid') @patch('swh.web.ui.views.browse.api') @istest def browse_directory_empty_result(self, mock_api): # given mock_api.api_directory.return_value = [] # when rv = self.client.get('/browse/directory/some-sha1/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('directory.html') self.assertEqual(self.get_context_variable('message'), 'Listing for directory some-sha1:') self.assertEqual(self.get_context_variable('files'), []) mock_api.api_directory.assert_called_once_with( 'some-sha1') @patch('swh.web.ui.views.browse.service') @patch('swh.web.ui.views.browse.api') @istest def browse_directory_relative_file(self, mock_api, mock_service): # given stub_entry = { 'sha256': '240', 'type': 'file' } mock_service.lookup_directory_with_path.return_value = stub_entry stub_file = { 'sha1_git': '123', 'sha1': '456', 'status': 'visible', 'data_url': '/api/1/content/890', 'length': 42, 'ctime': 'Thu, 01 Oct 2015 12:13:53 GMT', 'target': 'file.txt', 'sha256': '148' } mock_api.api_content_metadata.return_value = stub_file mock_service.lookup_content_raw.return_value = { 'data': 'this is my file'} # when rv = self.client.get('/browse/directory/sha1/path/to/file/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('content.html') self.assertIsNotNone(self.get_context_variable('content')) content = self.get_context_variable('content') # change caused by call to prepare_data_for_view self.assertEqual(content['data_url'], '/browse/content/890') self.assertEqual(content['data'], 'this is my file') mock_api.api_content_metadata.assert_called_once_with('sha256:240') mock_service.lookup_content_raw.assert_called_once_with('sha256:240') @patch('swh.web.ui.views.browse.service') @patch('swh.web.ui.views.browse.api') @istest def browse_directory_relative_dir(self, mock_api, mock_service): # given mock_service.lookup_directory_with_path.return_value = { 'sha256': '240', 'target': 'abcd', 'type': 'dir' } stub_directory_ls = [ {'type': 'dir', 'target': '123', 'name': 'some-dir-name'}, {'type': 'file', 'sha1': '654', 'name': 'some-filename'}, {'type': 'dir', 'target': '987', 'name': 'some-other-dirname'} ] mock_api.api_directory.return_value = stub_directory_ls # when rv = self.client.get('/browse/directory/sha1/path/to/dir/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('directory.html') self.assertIsNotNone(self.get_context_variable('files')) self.assertEqual(len(self.get_context_variable('files')), len(stub_directory_ls)) mock_api.api_directory.assert_called_once_with('abcd') @patch('swh.web.ui.views.browse.service') @patch('swh.web.ui.views.browse.api') @istest def browse_directory_relative_not_found(self, mock_api, mock_service): # given mock_service.lookup_directory_with_path.side_effect = NotFoundExc( 'Directory entry not found.') # when rv = self.client.get('/browse/directory/some-sha1/some/path/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('directory.html') self.assertEqual(self.get_context_variable('message'), 'Directory entry not found.') @patch('swh.web.ui.views.browse.api') @patch('swh.web.ui.views.browse.utils') @istest def browse_directory(self, mock_utils, mock_api): # given stub_directory_ls = [ {'type': 'dir', 'target': '123', 'name': 'some-dir-name'}, {'type': 'file', 'sha1': '654', 'name': 'some-filename'}, {'type': 'dir', 'target': '987', 'name': 'some-other-dirname'} ] mock_api.api_directory.return_value = stub_directory_ls stub_directory_map = [ {'link': '/path/to/url/dir/123', 'name': 'some-dir-name'}, {'link': '/path/to/url/file/654', 'name': 'some-filename'}, {'link': '/path/to/url/dir/987', 'name': 'some-other-dirname'} ] mock_utils.prepare_data_for_view.return_value = stub_directory_map # when rv = self.client.get('/browse/directory/some-sha1/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('directory.html') self.assertEqual(self.get_context_variable('message'), 'Listing for directory some-sha1:') self.assertEqual(self.get_context_variable('files'), stub_directory_map) mock_api.api_directory.assert_called_once_with( 'some-sha1') mock_utils.prepare_data_for_view.assert_called_once_with( stub_directory_ls) class ContentWithOriginView(test_app.SWHViewTestCase): render_template = False @patch('swh.web.ui.views.browse.api') # @istest def browse_content_with_origin_content_ko_not_found(self, mock_api): # given mock_api.api_content_checksum_to_origin.side_effect = NotFoundExc( 'Not found!') # when rv = self.client.get('/browse/content/sha256:some-sha256/origin/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('content-with-origin.html') self.assertEqual(self.get_context_variable('message'), 'Not found!') mock_api.api_content_checksum_to_origin.assert_called_once_with( 'sha256:some-sha256') @patch('swh.web.ui.views.browse.api') # @istest def browse_content_with_origin_ko_bad_input(self, mock_api): # given mock_api.api_content_checksum_to_origin.side_effect = BadInputExc( 'Invalid hash') # when rv = self.client.get('/browse/content/sha256:some-sha256/origin/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('content-with-origin.html') self.assertEqual( self.get_context_variable('message'), 'Invalid hash') mock_api.api_content_checksum_to_origin.assert_called_once_with( 'sha256:some-sha256') @patch('swh.web.ui.views.browse.api') # @istest def browse_content_with_origin(self, mock_api): # given mock_api.api_content_checksum_to_origin.return_value = { 'origin_type': 'ftp', 'origin_url': '/some/url', 'revision': 'revision-hash', 'branch': 'master', 'path': '/path/to', } # when rv = self.client.get('/browse/content/sha256:some-sha256/origin/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('content-with-origin.html') self.assertEqual( self.get_context_variable('message'), "The content with hash sha256:some-sha256 has been seen on " + "origin with type 'ftp'\n" + "at url '/some/url'. The revision was identified at " + "'revision-hash' on branch 'master'.\n" + "The file's path referenced was '/path/to'.") mock_api.api_content_checksum_to_origin.assert_called_once_with( 'sha256:some-sha256') class OriginView(test_app.SWHViewTestCase): render_template = False def setUp(self): def url_for_test(fn, **args): if fn == 'browse_revision_with_origin': return '/browse/revision/origin/%s/' % args['origin_id'] elif fn == 'api_origin_visits': return '/api/1/stat/visits/%s/' % args['origin_id'] self.url_for_test = url_for_test self.stub_origin = {'type': 'git', 'lister': None, 'project': None, 'url': 'rsync://some/url', 'id': 426} @patch('swh.web.ui.views.browse.api') @istest def browse_origin_ko_not_found(self, mock_api): # given mock_api.api_origin.side_effect = NotFoundExc('Not found!') # when rv = self.client.get('/browse/origin/1/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('origin.html') self.assertIsNone(self.get_context_variable('origin')) self.assertEqual( self.get_context_variable('message'), 'Not found!') mock_api.api_origin.assert_called_once_with(1, None, None) @patch('swh.web.ui.views.browse.api') @istest def browse_origin_ko_bad_input(self, mock_api): # given mock_api.api_origin.side_effect = BadInputExc('wrong input') # when rv = self.client.get('/browse/origin/426/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('origin.html') self.assertIsNone(self.get_context_variable('origin')) mock_api.api_origin.assert_called_once_with(426, None, None) @patch('swh.web.ui.views.browse.api') @patch('swh.web.ui.views.browse.url_for') @istest def browse_origin_found_id(self, mock_url_for, mock_api): # given mock_url_for.side_effect = self.url_for_test mock_api.api_origin.return_value = self.stub_origin # when rv = self.client.get('/browse/origin/426/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('origin.html') self.assertEqual(self.get_context_variable('origin'), self.stub_origin) self.assertEqual(self.get_context_variable('browse_url'), '/browse/revision/origin/426/') self.assertEqual(self.get_context_variable('visit_url'), '/api/1/stat/visits/426/') mock_api.api_origin.assert_called_once_with(426, None, None) @patch('swh.web.ui.views.browse.api') @patch('swh.web.ui.views.browse.url_for') @istest def browse_origin_found_url_type(self, mock_url_for, mock_api): # given mock_url_for.side_effect = self.url_for_test mock_api.api_origin.return_value = self.stub_origin # when rv = self.client.get('/browse/origin/git/url/rsync://some/url/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('origin.html') self.assertEqual(self.get_context_variable('origin'), self.stub_origin) self.assertEqual(self.get_context_variable('browse_url'), '/browse/revision/origin/426/') self.assertEqual(self.get_context_variable('visit_url'), '/api/1/stat/visits/426/') mock_api.api_origin.assert_called_once_with(None, 'git', 'rsync://some/url') class PersonView(test_app.SWHViewTestCase): render_template = False @patch('swh.web.ui.views.browse.api') @istest def browse_person_ko_not_found(self, mock_api): # given mock_api.api_person.side_effect = NotFoundExc('not found') # when rv = self.client.get('/browse/person/1/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('person.html') self.assertEqual(self.get_context_variable('person_id'), 1) self.assertEqual( self.get_context_variable('message'), 'not found') mock_api.api_person.assert_called_once_with(1) @patch('swh.web.ui.views.browse.api') @istest def browse_person_ko_bad_input(self, mock_api): # given mock_api.api_person.side_effect = BadInputExc('wrong input') # when rv = self.client.get('/browse/person/426/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('person.html') self.assertEqual(self.get_context_variable('person_id'), 426) mock_api.api_person.assert_called_once_with(426) @patch('swh.web.ui.views.browse.api') @istest def browse_person(self, mock_api): # given mock_person = {'type': 'git', 'lister': None, 'project': None, 'url': 'rsync://some/url', 'id': 426} mock_api.api_person.return_value = mock_person # when rv = self.client.get('/browse/person/426/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('person.html') self.assertEqual(self.get_context_variable('person_id'), 426) self.assertEqual(self.get_context_variable('person'), mock_person) mock_api.api_person.assert_called_once_with(426) class ReleaseView(test_app.SWHViewTestCase): render_template = False @patch('swh.web.ui.views.browse.api') @istest def browse_release_ko_not_found(self, mock_api): # given mock_api.api_release.side_effect = NotFoundExc('not found!') # when rv = self.client.get('/browse/release/1/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('release.html') self.assertEqual(self.get_context_variable('sha1_git'), '1') self.assertEqual( self.get_context_variable('message'), 'not found!') mock_api.api_release.assert_called_once_with('1') @patch('swh.web.ui.views.browse.api') @istest def browse_release_ko_bad_input(self, mock_api): # given mock_api.api_release.side_effect = BadInputExc('wrong input') # when rv = self.client.get('/browse/release/426/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('release.html') self.assertEqual(self.get_context_variable('sha1_git'), '426') mock_api.api_release.assert_called_once_with('426') @patch('swh.web.ui.views.browse.api') @istest def browse_release(self, mock_api): # given self.maxDiff = None mock_release = { "date": "Sun, 05 Jul 2015 18:02:06 GMT", "id": "1e951912027ea6873da6985b91e50c47f645ae1a", "target": "d770e558e21961ad6cfdf0ff7df0eb5d7d4f0754", "target_url": '/browse/revision/d770e558e21961ad6cfdf0ff7df0' 'eb5d7d4f0754/', "synthetic": False, "target_type": "revision", "author": { "email": "torvalds@linux-foundation.org", "name": "Linus Torvalds" }, "message": "Linux 4.2-rc1\n", "name": "v4.2-rc1" } mock_api.api_release.return_value = mock_release expected_release = { "date": "Sun, 05 Jul 2015 18:02:06 GMT", "id": "1e951912027ea6873da6985b91e50c47f645ae1a", "target_url": '/browse/revision/d770e558e21961ad6cfdf0ff7df0' 'eb5d7d4f0754/', "target": 'd770e558e21961ad6cfdf0ff7df0eb5d7d4f0754', "synthetic": False, "target_type": "revision", "author": { "email": "torvalds@linux-foundation.org", "name": "Linus Torvalds" }, "message": "Linux 4.2-rc1\n", "name": "v4.2-rc1" } # when rv = self.client.get('/browse/release/426/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('release.html') self.assertEqual(self.get_context_variable('sha1_git'), '426') self.assertEqual(self.get_context_variable('release'), expected_release) mock_api.api_release.assert_called_once_with('426') class RevisionView(test_app.SWHViewTestCase): render_template = False @patch('swh.web.ui.views.browse.api') @istest def browse_revision_ko_not_found(self, mock_api): # given mock_api.api_revision.side_effect = NotFoundExc('Not found!') # when rv = self.client.get('/browse/revision/1/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision.html') self.assertEqual(self.get_context_variable('sha1_git'), '1') self.assertEqual( self.get_context_variable('message'), 'Not found!') self.assertIsNone(self.get_context_variable('revision')) mock_api.api_revision.assert_called_once_with('1', None) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_ko_bad_input(self, mock_api): # given mock_api.api_revision.side_effect = BadInputExc('wrong input!') # when rv = self.client.get('/browse/revision/426/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision.html') self.assertEqual(self.get_context_variable('sha1_git'), '426') self.assertEqual( self.get_context_variable('message'), 'wrong input!') self.assertIsNone(self.get_context_variable('revision')) mock_api.api_revision.assert_called_once_with('426', None) @patch('swh.web.ui.views.browse.api') @istest def browse_revision(self, mock_api): # given stub_revision = { 'id': 'd770e558e21961ad6cfdf0ff7df0eb5d7d4f0754', 'date': 'Sun, 05 Jul 2015 18:01:52 GMT', 'committer': { 'email': 'torvalds@linux-foundation.org', 'name': 'Linus Torvalds' }, 'committer_date': 'Sun, 05 Jul 2015 18:01:52 GMT', 'type': 'git', 'author': { 'email': 'torvalds@linux-foundation.org', 'name': 'Linus Torvalds' }, 'message': 'Linux 4.2-rc1\n', 'synthetic': False, 'directory_url': '/api/1/directory/' '2a1dbabeed4dcf1f4a4c441993b2ffc9d972780b/', 'parent_url': [ '/api/1/revision/a585d2b738bfa26326b3f1f40f0f1eda0c067ccf/' ], } mock_api.api_revision.return_value = stub_revision expected_revision = { 'id': 'd770e558e21961ad6cfdf0ff7df0eb5d7d4f0754', 'date': 'Sun, 05 Jul 2015 18:01:52 GMT', 'committer': { 'email': 'torvalds@linux-foundation.org', 'name': 'Linus Torvalds' }, 'committer_date': 'Sun, 05 Jul 2015 18:01:52 GMT', 'type': 'git', 'author': { 'email': 'torvalds@linux-foundation.org', 'name': 'Linus Torvalds' }, 'message': 'Linux 4.2-rc1\n', 'synthetic': False, 'parent_url': [ '/browse/revision/a585d2b738bfa26326b3f1f40f0f1eda0c067ccf/' ], 'directory_url': '/browse/directory/2a1dbabeed4dcf1f4a4c441993b2f' 'fc9d972780b/', } # when rv = self.client.get('/browse/revision/426/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision.html') self.assertEqual(self.get_context_variable('sha1_git'), '426') self.assertEqual(self.get_context_variable('revision'), expected_revision) self.assertIsNone(self.get_context_variable('message')) mock_api.api_revision.assert_called_once_with('426', None) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_raw_message(self, mock_api): # given sha1 = 'd770e558e21961ad6cfdf0ff7df0eb5d7d4f0754' # when rv = self.client.get('/browse/revision/' 'd770e558e21961ad6cfdf0ff7df0eb5d7d4f0754/raw/') self.assertRedirects( rv, '/api/1/revision/%s/raw/' % sha1) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_log_ko_not_found(self, mock_api): # given mock_api.api_revision_log.side_effect = NotFoundExc('Not found!') # when rv = self.client.get('/browse/revision/sha1/log/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision-log.html') self.assertEqual(self.get_context_variable('sha1_git'), 'sha1') self.assertEqual( self.get_context_variable('message'), 'Not found!') self.assertEqual(self.get_context_variable('revisions'), []) mock_api.api_revision_log.assert_called_once_with('sha1', None) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_log_ko_bad_input(self, mock_api): # given mock_api.api_revision_log.side_effect = BadInputExc('wrong input!') # when rv = self.client.get('/browse/revision/426/log/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision-log.html') self.assertEqual(self.get_context_variable('sha1_git'), '426') self.assertEqual( self.get_context_variable('message'), 'wrong input!') self.assertEqual(self.get_context_variable('revisions'), []) mock_api.api_revision_log.assert_called_once_with('426', None) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_log(self, mock_api): # given stub_revisions = { 'revisions': [{ 'id': 'd770e558e21961ad6cfdf0ff7df0eb5d7d4f0754', 'date': 'Sun, 05 Jul 2015 18:01:52 GMT', 'committer': { 'email': 'torvalds@linux-foundation.org', 'name': 'Linus Torvalds' }, 'committer_date': 'Sun, 05 Jul 2015 18:01:52 GMT', 'type': 'git', 'author': { 'email': 'torvalds@linux-foundation.org', 'name': 'Linus Torvalds' }, 'message': 'Linux 4.2-rc1\n', 'synthetic': False, 'directory_url': '/api/1/directory/' '2a1dbabeed4dcf1f4a4c441993b2ffc9d972780b/', 'parent_url': [ '/api/1/revision/a585d2b738bfa26326b3f1f40f0f1eda0c067ccf/' ], }], 'next_revs_url': '/api/1/revision/1234/log/' } mock_api.api_revision_log.return_value = stub_revisions # when rv = self.client.get('/browse/revision/426/log/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision-log.html') self.assertEqual(self.get_context_variable('sha1_git'), '426') self.assertTrue( isinstance(self.get_context_variable('revisions'), map)) self.assertEqual( self.get_context_variable('next_revs_url'), '/browse/revision/1234/log/') self.assertIsNone(self.get_context_variable('message')) mock_api.api_revision_log.assert_called_once_with('426', None) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_log_by_ko_not_found(self, mock_api): # given mock_api.api_revision_log_by.side_effect = NotFoundExc('Not found!') # when rv = self.client.get('/browse/revision/origin/9/log/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision-log.html') self.assertEqual(self.get_context_variable('origin_id'), 9) self.assertEqual( self.get_context_variable('message'), 'Not found!') self.assertEqual(self.get_context_variable('revisions'), []) mock_api.api_revision_log_by.assert_called_once_with( 9, 'refs/heads/master', None) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_log_by_ko_bad_input(self, mock_api): # given mock_api.api_revision_log.side_effect = BadInputExc('wrong input!') # when rv = self.client.get('/browse/revision/abcd/log/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision-log.html') self.assertEqual(self.get_context_variable('sha1_git'), 'abcd') self.assertEqual( self.get_context_variable('message'), 'wrong input!') self.assertEqual(self.get_context_variable('revisions'), []) mock_api.api_revision_log.assert_called_once_with('abcd', None) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_log_by(self, mock_api): # given stub_revisions = [{ 'id': 'd770e558e21961ad6cfdf0ff7df0eb5d7d4f0754', 'date': 'Sun, 05 Jul 2015 18:01:52 GMT', 'committer': { 'email': 'torvalds@linux-foundation.org', 'name': 'Linus Torvalds' }, 'committer_date': 'Sun, 05 Jul 2015 18:01:52 GMT', 'type': 'git', 'author': { 'email': 'torvalds@linux-foundation.org', 'name': 'Linus Torvalds' }, 'message': 'Linux 4.2-rc1\n', 'synthetic': False, 'directory_url': '/api/1/directory/' '2a1dbabeed4dcf1f4a4c441993b2ffc9d972780b/', 'parent_url': [ '/api/1/revision/a585d2b738bfa26326b3f1f40f0f1eda0c067ccf/' ], }] mock_api.api_revision_log_by.return_value = stub_revisions # when rv = self.client.get('/browse/revision/origin/2/log/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision-log.html') self.assertEqual(self.get_context_variable('origin_id'), 2) self.assertTrue( isinstance(self.get_context_variable('revisions'), map)) self.assertIsNone(self.get_context_variable('message')) mock_api.api_revision_log_by.assert_called_once_with( 2, 'refs/heads/master', None) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_history_ko_not_found(self, mock_api): # given mock_api.api_revision_history.side_effect = NotFoundExc( 'Not found') # when rv = self.client.get('/browse/revision/1/history/2/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision.html') self.assertEqual(self.get_context_variable('sha1_git_root'), '1') self.assertEqual(self.get_context_variable('sha1_git'), '2') self.assertEqual( self.get_context_variable('message'), 'Not found') mock_api.api_revision_history.assert_called_once_with( '1', '2') @patch('swh.web.ui.views.browse.api') @istest def browse_revision_history_ko_bad_input(self, mock_api): # given mock_api.api_revision_history.side_effect = BadInputExc( 'Input incorrect') # when rv = self.client.get('/browse/revision/321/history/654/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision.html') self.assertEqual(self.get_context_variable('sha1_git_root'), '321') self.assertEqual(self.get_context_variable('sha1_git'), '654') self.assertEqual( self.get_context_variable('message'), 'Input incorrect') mock_api.api_revision_history.assert_called_once_with( '321', '654') @istest def browse_revision_history_ok_same_sha1(self): # when rv = self.client.get('/browse/revision/10/history/10/') # then self.assertEqual(rv.status_code, 302) @patch('swh.web.ui.views.browse.utils') @patch('swh.web.ui.views.browse.api') @istest def browse_revision_history(self, mock_api, mock_utils): # given stub_revision = {'id': 'some-rev'} mock_api.api_revision_history.return_value = stub_revision expected_revision = { 'id': 'some-rev-id', 'author': {'name': 'foo', 'email': 'bar'}, 'committer': {'name': 'foo', 'email': 'bar'} } mock_utils.prepare_data_for_view.return_value = expected_revision # when rv = self.client.get('/browse/revision/426/history/789/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision.html') self.assertEqual(self.get_context_variable('sha1_git_root'), '426') self.assertEqual(self.get_context_variable('sha1_git'), '789') self.assertEqual(self.get_context_variable('revision'), expected_revision) mock_api.api_revision_history.assert_called_once_with( '426', '789') mock_utils.prepare_data_for_view.assert_called_once_with(stub_revision) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_directory_ko_not_found(self, mock_api): # given mock_api.api_revision_directory.side_effect = NotFoundExc('Not found!') # when rv = self.client.get('/browse/revision/1/directory/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision-directory.html') self.assertEqual(self.get_context_variable('sha1_git'), '1') self.assertEqual(self.get_context_variable('path'), '.') self.assertIsNone(self.get_context_variable('result')) self.assertEqual( self.get_context_variable('message'), "Not found!") mock_api.api_revision_directory.assert_called_once_with( '1', None, with_data=True) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_directory_ko_bad_input(self, mock_api): # given mock_api.api_revision_directory.side_effect = BadInputExc('Bad input!') # when rv = self.client.get('/browse/revision/10/directory/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision-directory.html') self.assertEqual(self.get_context_variable('sha1_git'), '10') self.assertEqual(self.get_context_variable('path'), '.') self.assertIsNone(self.get_context_variable('result')) self.assertEqual( self.get_context_variable('message'), "Bad input!") mock_api.api_revision_directory.assert_called_once_with( '10', None, with_data=True) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_directory(self, mock_api): # given stub_result0 = { 'type': 'dir', 'revision': '100', 'content': [ { 'id': 'some-result', 'type': 'file', 'name': 'blah', }, { 'id': 'some-other-result', 'type': 'dir', 'name': 'foo', } ] } mock_api.api_revision_directory.return_value = stub_result0 stub_result1 = { 'type': 'dir', 'revision': '100', 'content': [ { 'id': 'some-result', 'type': 'file', 'name': 'blah', }, { 'id': 'some-other-result', 'type': 'dir', 'name': 'foo', } ] } # when rv = self.client.get('/browse/revision/100/directory/some/path/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision-directory.html') self.assertEqual(self.get_context_variable('sha1_git'), '100') self.assertEqual(self.get_context_variable('revision'), '100') self.assertEqual(self.get_context_variable('path'), 'some/path') self.assertIsNone(self.get_context_variable('message')) self.assertEqual(self.get_context_variable('result'), stub_result1) mock_api.api_revision_directory.assert_called_once_with( '100', 'some/path', with_data=True) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_history_directory_ko_not_found(self, mock_api): # given mock_api.api_revision_history_directory.side_effect = NotFoundExc( 'not found') # when rv = self.client.get('/browse/revision/123/history/456/directory/a/b/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision-directory.html') self.assertEqual(self.get_context_variable('sha1_git_root'), '123') self.assertEqual(self.get_context_variable('sha1_git'), '456') self.assertEqual(self.get_context_variable('path'), 'a/b') self.assertEqual(self.get_context_variable('message'), 'not found') self.assertIsNone(self.get_context_variable('result')) mock_api.api_revision_history_directory.assert_called_once_with( '123', '456', 'a/b', with_data=True) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_history_directory_ko_bad_input(self, mock_api): # given mock_api.api_revision_history_directory.side_effect = BadInputExc( 'bad input') # when rv = self.client.get('/browse/revision/123/history/456/directory/a/c/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision-directory.html') self.assertEqual(self.get_context_variable('sha1_git_root'), '123') self.assertEqual(self.get_context_variable('sha1_git'), '456') self.assertEqual(self.get_context_variable('path'), 'a/c') self.assertEqual(self.get_context_variable('message'), 'bad input') self.assertIsNone(self.get_context_variable('result')) mock_api.api_revision_history_directory.assert_called_once_with( '123', '456', 'a/c', with_data=True) @patch('swh.web.ui.views.browse.service') @istest def browse_revision_history_directory_ok_no_trailing_slash_so_redirect( self, mock_service): # when rv = self.client.get('/browse/revision/1/history/2/directory/path/to') # then self.assertEqual(rv.status_code, 301) @patch('swh.web.ui.views.browse.service') @istest def browse_revision_history_directory_ok_same_sha1_redirects( self, mock_service): # when rv = self.client.get('/browse/revision/1/history/1/directory/path/to') # then self.assertEqual(rv.status_code, 301) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_history_directory(self, mock_api): # given stub_result0 = { 'type': 'dir', 'revision': '1000', 'content': [{ 'id': 'some-result', 'type': 'file', 'name': 'blah' }] } mock_api.api_revision_history_directory.return_value = stub_result0 stub_result1 = { 'type': 'dir', 'revision': '1000', 'content': [{ 'id': 'some-result', 'type': 'file', 'name': 'blah' }] } # when rv = self.client.get('/browse/revision/100/history/999/directory/' 'path/to/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision-directory.html') self.assertEqual(self.get_context_variable('sha1_git_root'), '100') self.assertEqual(self.get_context_variable('sha1_git'), '999') self.assertEqual(self.get_context_variable('revision'), '1000') self.assertEqual(self.get_context_variable('path'), 'path/to') self.assertIsNone(self.get_context_variable('message')) self.assertEqual(self.get_context_variable('result'), stub_result1) mock_api.api_revision_history_directory.assert_called_once_with( '100', '999', 'path/to', with_data=True) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_history_through_origin_ko_bad_input(self, mock_api): # given mock_api.api_revision_history_through_origin.side_effect = BadInputExc( 'Problem input.') # noqa # when rv = self.client.get('/browse/revision/origin/99' '/history/123/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision.html') self.assertIsNone(self.get_context_variable('revision')) self.assertEqual(self.get_context_variable('message'), 'Problem input.') mock_api.api_revision_history_through_origin.assert_called_once_with( 99, 'refs/heads/master', None, '123') @patch('swh.web.ui.views.browse.api') @istest def browse_revision_history_through_origin_ko_not_found(self, mock_api): # given mock_api.api_revision_history_through_origin.side_effect = NotFoundExc( 'Not found.') # when rv = self.client.get('/browse/revision/origin/999/' 'branch/dev/history/123/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision.html') self.assertIsNone(self.get_context_variable('revision')) self.assertEqual(self.get_context_variable('message'), 'Not found.') mock_api.api_revision_history_through_origin.assert_called_once_with( 999, 'dev', None, '123') @patch('swh.web.ui.views.browse.api') @istest def browse_revision_history_through_origin_ko_other_error(self, mock_api): # given mock_api.api_revision_history_through_origin.side_effect = ValueError( 'Other Error.') # when rv = self.client.get('/browse/revision/origin/438' '/branch/scratch' '/ts/2016' '/history/789/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision.html') self.assertIsNone(self.get_context_variable('revision')) self.assertEqual(self.get_context_variable('message'), 'Other Error.') mock_api.api_revision_history_through_origin.assert_called_once_with( 438, 'scratch', '2016', '789') @patch('swh.web.ui.views.browse.api') @istest def browse_revision_history_through_origin(self, mock_api): # given stub_rev = { 'id': 'some-id', 'author': {}, 'committer': {} } mock_api.api_revision_history_through_origin.return_value = stub_rev # when rv = self.client.get('/browse/revision/origin/99/history/123/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision.html') self.assertEqual(self.get_context_variable('revision'), stub_rev) self.assertIsNone(self.get_context_variable('message')) mock_api.api_revision_history_through_origin.assert_called_once_with( 99, 'refs/heads/master', None, '123') @patch('swh.web.ui.views.browse.api') @istest def browse_revision_with_origin_ko_not_found(self, mock_api): # given mock_api.api_revision_with_origin.side_effect = NotFoundExc( 'Not found') # when rv = self.client.get('/browse/revision/origin/1/') self.assertEqual(rv.status_code, 200) self.assert_template_used('revision.html') self.assertIsNone(self.get_context_variable('revision')) self.assertEqual(self.get_context_variable('message'), 'Not found') mock_api.api_revision_with_origin.assert_called_once_with( 1, 'refs/heads/master', None) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_with_origin_ko_bad_input(self, mock_api): # given mock_api.api_revision_with_origin.side_effect = BadInputExc( 'Bad Input') # when rv = self.client.get('/browse/revision/origin/1000/branch/dev/') self.assertEqual(rv.status_code, 200) self.assert_template_used('revision.html') self.assertIsNone(self.get_context_variable('revision')) self.assertEqual(self.get_context_variable('message'), 'Bad Input') mock_api.api_revision_with_origin.assert_called_once_with( 1000, 'dev', None) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_with_origin_ko_other(self, mock_api): # given mock_api.api_revision_with_origin.side_effect = ValueError( 'Other') # when rv = self.client.get('/browse/revision/origin/1999' '/branch/scratch/master' '/ts/1990-01-10/') self.assertEqual(rv.status_code, 200) self.assert_template_used('revision.html') self.assertIsNone(self.get_context_variable('revision')) self.assertEqual(self.get_context_variable('message'), 'Other') mock_api.api_revision_with_origin.assert_called_once_with( 1999, 'scratch/master', '1990-01-10') @patch('swh.web.ui.views.browse.api') @istest def browse_revision_with_origin(self, mock_api): # given stub_rev = {'id': 'some-id', 'author': {}, 'committer': {}} mock_api.api_revision_with_origin.return_value = stub_rev # when rv = self.client.get('/browse/revision/origin/1/') self.assertEqual(rv.status_code, 200) self.assert_template_used('revision.html') self.assertEqual(self.get_context_variable('revision'), stub_rev) self.assertIsNone(self.get_context_variable('message')) mock_api.api_revision_with_origin.assert_called_once_with( 1, 'refs/heads/master', None) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_directory_through_origin_ko_not_found(self, mock_api): # given mock_api.api_directory_through_revision_origin.side_effect = BadInputExc( # noqa 'this is not the robot you are looking for') # when rv = self.client.get('/browse/revision/origin/2' '/directory/') self.assertEqual(rv.status_code, 200) self.assert_template_used('revision-directory.html') self.assertIsNone(self.get_context_variable('result')) self.assertEqual(self.get_context_variable('message'), 'this is not the robot you are looking for') mock_api.api_directory_through_revision_origin.assert_called_once_with( # noqa 2, 'refs/heads/master', None, None, with_data=True) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_directory_through_origin_ko_bad_input(self, mock_api): # given mock_api.api_directory_through_revision_origin.side_effect = BadInputExc( # noqa 'Bad Robot') # when rv = self.client.get('/browse/revision/origin/2' '/directory/') self.assertEqual(rv.status_code, 200) self.assert_template_used('revision-directory.html') self.assertIsNone(self.get_context_variable('result')) self.assertEqual(self.get_context_variable('message'), 'Bad Robot') mock_api.api_directory_through_revision_origin.assert_called_once_with( 2, 'refs/heads/master', None, None, with_data=True) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_directory_through_origin_ko_other(self, mock_api): # given mock_api.api_directory_through_revision_origin.side_effect = ValueError( # noqa 'Other bad stuff') # when rv = self.client.get('/browse/revision/origin/2' '/directory/') self.assertEqual(rv.status_code, 200) self.assert_template_used('revision-directory.html') self.assertIsNone(self.get_context_variable('result')) self.assertEqual(self.get_context_variable('message'), 'Other bad stuff') mock_api.api_directory_through_revision_origin.assert_called_once_with( 2, 'refs/heads/master', None, None, with_data=True) @patch('swh.web.ui.views.browse.api') @istest def browse_revision_directory_through_origin(self, mock_api): # given stub_res = {'id': 'some-id', 'revision': 'some-rev-id', 'type': 'dir', 'content': 'some-content'} mock_api.api_directory_through_revision_origin.return_value = stub_res # when rv = self.client.get('/browse/revision/origin/2' '/branch/dev' '/ts/2013-20-20 10:02' '/directory/some/file/') self.assertEqual(rv.status_code, 200) self.assert_template_used('revision-directory.html') self.assertEqual(self.get_context_variable('result'), stub_res) self.assertIsNone(self.get_context_variable('message')) mock_api.api_directory_through_revision_origin.assert_called_once_with( 2, 'dev', '2013-20-20 10:02', 'some/file', with_data=True) @patch('swh.web.ui.views.browse.api') @istest def browse_directory_through_revision_with_origin_history_ko_not_found( self, mock_api): mock_api.api_directory_through_revision_with_origin_history.side_effect = NotFoundExc( # noqa 'Not found!') # when rv = self.client.get('/browse/revision/origin/987' '/history/sha1git' '/directory/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision-directory.html') self.assertIsNone(self.get_context_variable('result')) self.assertEqual(self.get_context_variable('message'), 'Not found!') self.assertEqual(self.get_context_variable('path'), '.') mock_api.api_directory_through_revision_with_origin_history.assert_called_once_with( # noqa 987, 'refs/heads/master', None, 'sha1git', None, with_data=True) @patch('swh.web.ui.views.browse.api') @istest def browse_directory_through_revision_with_origin_history_ko_bad_input( self, mock_api): mock_api.api_directory_through_revision_with_origin_history.side_effect = BadInputExc( # noqa 'Bad input! Bleh!') # when rv = self.client.get('/browse/revision/origin/798' '/branch/refs/heads/dev' '/ts/2012-11-11' '/history/1234' '/directory/some/path/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision-directory.html') self.assertIsNone(self.get_context_variable('result')) self.assertEqual(self.get_context_variable('message'), 'Bad input! Bleh!') self.assertEqual(self.get_context_variable('path'), 'some/path') mock_api.api_directory_through_revision_with_origin_history.assert_called_once_with( # noqa 798, 'refs/heads/dev', '2012-11-11', '1234', 'some/path', with_data=True) @patch('swh.web.ui.views.browse.api') @istest def browse_directory_through_revision_with_origin_history( self, mock_api): stub_dir = {'type': 'dir', 'content': [], 'revision': 'specific-rev-id'} mock_api.api_directory_through_revision_with_origin_history.return_value = stub_dir # noqa # when rv = self.client.get('/browse/revision/origin/101010' '/ts/1955-11-12' '/history/54628' '/directory/emacs-24.5/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('revision-directory.html') self.assertEqual(self.get_context_variable('result'), stub_dir) self.assertIsNone(self.get_context_variable('message')) self.assertEqual(self.get_context_variable('path'), 'emacs-24.5') mock_api.api_directory_through_revision_with_origin_history.assert_called_once_with( # noqa 101010, 'refs/heads/master', '1955-11-12', '54628', 'emacs-24.5', with_data=True) class EntityView(test_app.SWHViewTestCase): render_template = False @patch('swh.web.ui.views.browse.api') @istest def browse_entity_ko_not_found(self, mock_api): # given mock_api.api_entity_by_uuid.side_effect = NotFoundExc('Not found!') # when rv = self.client.get('/browse/entity/' '5f4d4c51-498a-4e28-88b3-b3e4e8396cba/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('entity.html') self.assertEqual(self.get_context_variable('entities'), []) self.assertEqual(self.get_context_variable('message'), 'Not found!') mock_api.api_entity_by_uuid.assert_called_once_with( '5f4d4c51-498a-4e28-88b3-b3e4e8396cba') @patch('swh.web.ui.views.browse.api') @istest def browse_entity_ko_bad_input(self, mock_api): # given mock_api.api_entity_by_uuid.side_effect = BadInputExc('wrong input!') # when rv = self.client.get('/browse/entity/blah-blah-uuid/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('entity.html') self.assertEqual(self.get_context_variable('entities'), []) self.assertEqual(self.get_context_variable('message'), 'wrong input!') mock_api.api_entity_by_uuid.assert_called_once_with( 'blah-blah-uuid') @patch('swh.web.ui.views.browse.api') @istest def browse_entity(self, mock_api): # given stub_entities = [ {'id': '5f4d4c51-5a9b-4e28-88b3-b3e4e8396cba'}] mock_api.api_entity_by_uuid.return_value = stub_entities # when rv = self.client.get('/browse/entity/' '5f4d4c51-5a9b-4e28-88b3-b3e4e8396cba/') # then self.assertEqual(rv.status_code, 200) self.assert_template_used('entity.html') self.assertEqual(self.get_context_variable('entities'), stub_entities) self.assertIsNone(self.get_context_variable('message')) mock_api.api_entity_by_uuid.assert_called_once_with( '5f4d4c51-5a9b-4e28-88b3-b3e4e8396cba') class Lookup(TestCase): @patch('swh.web.ui.views.browse.api') @istest def api_lookup(self, mock_api): # given mock_api.api_content_metadata.return_value = {'id': 'blah'} # given r = browse.api_lookup(mock_api.api_content_metadata, 'sha1:blah') # then self.assertEquals(r, {'id': 'blah'}) mock_api.api_content_metadata.assert_called_once_with('sha1:blah') @patch('swh.web.ui.views.browse.api') @istest def api_lookup_not_found(self, mock_api): # given mock_api.api_content_filetype.side_effect = NotFoundExc # given r = browse.api_lookup(mock_api.api_content_filetype, 'sha1_git:foo') # then self.assertIsNone(r) mock_api.api_content_filetype.assert_called_once_with('sha1_git:foo') @patch('swh.web.ui.views.browse.api') @istest def api_lookup_bad_input(self, mock_api): # given mock_api.api_content_license.side_effect = BadInputExc # given r = browse.api_lookup(mock_api.api_content_license, 'sha1_git:foo') # then self.assertIsNone(r) mock_api.api_content_license.assert_called_once_with('sha1_git:foo') diff --git a/swh/web/ui/views/browse.py b/swh/web/ui/views/browse.py index a320319d..35f08d69 100644 --- a/swh/web/ui/views/browse.py +++ b/swh/web/ui/views/browse.py @@ -1,1023 +1,1032 @@ # Copyright (C) 2015-2016 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information from encodings.aliases import aliases from flask import render_template, request, url_for, redirect from swh.core.hashutil import ALGORITHMS from swh.core.utils import grouper from .. import service, utils, apidoc from ..exc import BadInputExc, NotFoundExc from ..main import app from . import api hash_filter_keys = ALGORITHMS def api_lookup(api_fn, query): """Lookup api with api_fn function with parameter query. Example: filetype = api_lookup('api_content_filetype', 'sha1:blah') if filetype: content['mimetype'] = filetype['mimetype'] """ try: return api_fn(query) except (NotFoundExc, BadInputExc): return None @app.route('/origin/search/') def search_origin(): """ Redirect request with GET params for an origin to our fragmented URI scheme """ if request.method == 'GET': data = request.args origin_id = data.get('origin_id') if origin_id: return redirect(url_for('browse_origin', origin_id=origin_id)) args = ['origin_type', 'origin_url'] values = {arg: data.get(arg) for arg in args if data.get(arg)} if 'origin_type' in values and 'origin_url' in values: return redirect(url_for('browse_origin', **values)) @app.route('/directory/search/') def search_directory(): """ Redirect request with GET params for a directory to our fragmented URI scheme """ def url_for_filtered(endpoint, **kwargs): """Make url_for ignore keyword args that have an empty string for value """ filtered = {k: v for k, v in kwargs.items() if kwargs[k]} return url_for(endpoint, **filtered) if request.method == 'GET': data = request.args sha1_git = data.get('sha1_git') if sha1_git: if 'dir_path' in data: # dir_path exists only in requests for a revision's directory return redirect(url_for_filtered( 'browse_revision_directory', sha1_git=sha1_git, dir_path=data.get('dir_path') )) return redirect(url_for_filtered( 'browse_directory', sha1_git=sha1_git, path=data.get('path') )) args = ['origin_id', 'branch_name', 'ts', 'path'] values = {arg: data.get(arg) for arg in args if data.get(arg)} if 'origin_id' in values: return redirect(url_for('browse_revision_directory_through_origin', **values)) @app.route('/revision/search/') def search_revision(): """ Redirect request with GET params for a revision to our fragmented URI scheme """ if request.method == 'GET': data = request.args sha1_git = data.get('sha1_git') if sha1_git: return redirect(url_for('browse_revision', sha1_git=sha1_git)) args = ['origin_id', 'branch_name', 'ts'] values = {arg: data.get(arg) for arg in args if data.get(arg)} if 'origin_id' in values: return redirect(url_for('browse_revision_with_origin', **values)) @app.route('/content/symbol/', methods=['GET']) def search_symbol(): """Search for symbols in contents. Returns: dict representing data to look for in swh storage. """ env = { 'result': None, 'per_page': None, 'message': '', 'linknext': None, 'linkprev': None, } # Read form or get information data = request.args q = data.get('q') per_page = data.get('per_page') env['q'] = q if per_page: env['per_page'] = per_page if q: try: result = api.api_content_symbol(q) if result: headers = result.get('headers') result = utils.prepare_data_for_view(result['results']) env['result'] = result if headers: url = url_for('search_symbol') if 'link-next' in headers: next_last_sha1 = result[-1]['sha1'] if per_page: params = (('q', q), ('last_sha1', next_last_sha1), ('per_page', per_page)) else: params = (('q', q), ('last_sha1', next_last_sha1)) env['linknext'] = utils.to_url(url, params) except BadInputExc as e: env['message'] = str(e) return render_template('symbols.html', **env) @app.route('/content/search/', methods=['GET', 'POST']) def search_content(): """Search for hashes in swh-storage. One form to submit either: - hash query to look up in swh storage - file hashes calculated client-side to be queried in swh storage - both Returns: dict representing data to look for in swh storage. The following keys are returned: - search_stats: {'nbfiles': X, 'pct': Y} the number of total queried files and percentage of files not in storage respectively - responses: array of {'filename': X, 'sha1': Y, 'found': Z} - messages: General messages. TODO: Batch-process with all checksums, not just sha1 """ env = {'search_res': None, 'search_stats': None, 'message': []} search_stats = {'nbfiles': 0, 'pct': 0} search_res = None message = '' # Get with a single hash request if request.method == 'POST': # Post form submission with many hash requests q = None else: data = request.args q = data.get('q') try: search = api.api_search(q) search_res = search['search_res'] search_stats = search['search_stats'] except BadInputExc as e: message = str(e) env['search_stats'] = search_stats env['search_res'] = search_res env['message'] = message return render_template('search.html', **env) @app.route('/browse/') def browse(): """Render the user-facing browse view """ return render_template('browse.html') -@app.route('/api/') -def browse_api_doc(): - """Render the API's documentation. +@app.route('/api/1/') +def browse_api_endpoints(): + """Display the list of opened api endpoints. + """ routes = apidoc.APIUrls.get_app_endpoints() # Return a list of routes with consistent ordering env = { 'doc_routes': sorted(routes.items()) } - return render_template('api.html', **env) + return render_template('api-endpoints.html', **env) + + +@app.route('/api/') +def browse_api_doc(): + """Display the API's documentation. + + """ + return render_template('api.html') @app.route('/browse/content//') def browse_content(q): """Given a hash and a checksum, display the content's meta-data. Args: q is of the form algo_hash:hash with algo_hash in (sha1, sha1_git, sha256) Returns: Information on one possible origin for such content. Raises: BadInputExc in case of unknown algo_hash or bad hash NotFoundExc if the content is not found. """ env = {'q': q, 'message': None, 'content': None} encoding = request.args.get('encoding', 'utf8') if encoding not in aliases: env['message'] = 'Encoding %s not supported.' \ 'Supported Encodings: %s' % ( encoding, list(aliases.keys())) return render_template('content.html', **env) try: content = api.api_content_metadata(q) filetype = api_lookup(api.api_content_filetype, q) if filetype: content['mimetype'] = filetype.get('mimetype') content['encoding'] = filetype.get('encoding') else: content['mimetype'] = None content['encoding'] = None language = api_lookup(api.api_content_language, q) if language: content['language'] = language.get('lang') else: content['language'] = None licenses = api_lookup(api.api_content_license, q) if licenses: content['licenses'] = ', '.join(licenses.get('licenses', [])) else: content['licenses'] = None content_raw = service.lookup_content_raw(q) if content_raw: content['data'] = content_raw['data'] else: content['data'] = None ctags = api_lookup(api.api_content_ctags, q) if ctags: url = url_for('browse_content', q=q) content['ctags'] = grouper(( '%s' % ( url, ctag['line'], ctag['line']) for ctag in ctags ), 20) else: content['ctags'] = None env['content'] = utils.prepare_data_for_view(content, encoding=encoding) except (NotFoundExc, BadInputExc) as e: env['message'] = str(e) return render_template('content.html', **env) @app.route('/browse/content//raw/') def browse_content_raw(q): """Given a hash and a checksum, display the content's raw data. Args: q is of the form algo_hash:hash with algo_hash in (sha1, sha1_git, sha256) Returns: Information on one possible origin for such content. Raises: BadInputExc in case of unknown algo_hash or bad hash NotFoundExc if the content is not found. """ return redirect(url_for('api_content_raw', q=q)) def _origin_seen(q, data): """Given an origin, compute a message string with the right information. Args: origin: a dictionary with keys: - origin: a dictionary with type and url keys - occurrence: a dictionary with a validity range Returns: Message as a string """ origin_type = data['origin_type'] origin_url = data['origin_url'] revision = data['revision'] branch = data['branch'] path = data['path'] return """The content with hash %s has been seen on origin with type '%s' at url '%s'. The revision was identified at '%s' on branch '%s'. The file's path referenced was '%s'.""" % (q, origin_type, origin_url, revision, branch, path) # @app.route('/browse/content//origin/') def browse_content_with_origin(q): """Show content information. Args: - q: query string of the form with `algo_hash` in sha1, sha1_git, sha256. This means that several different URLs (at least one per HASH_ALGO) will point to the same content sha: the sha with 'hash' format Returns: The content's information at for a given checksum. """ env = {'q': q} try: origin = api.api_content_checksum_to_origin(q) message = _origin_seen(q, origin) except (NotFoundExc, BadInputExc) as e: message = str(e) env['message'] = message return render_template('content-with-origin.html', **env) @app.route('/browse/directory//') @app.route('/browse/directory///') def browse_directory(sha1_git, path=None): """Show directory information. Args: - sha1_git: the directory's sha1 git identifier. If path is set, the base directory for the relative path to the entry - path: the path to the requested entry, relative to the directory pointed by sha1_git Returns: The content's information at sha1_git, or at sha1_git/path if path is set. """ env = {'sha1_git': sha1_git, 'files': []} try: if path: env['message'] = ('Listing for directory with path %s from %s:' % (path, sha1_git)) dir_or_file = service.lookup_directory_with_path( sha1_git, path) if dir_or_file['type'] == 'file': fsha = 'sha256:%s' % dir_or_file['sha256'] content = api.api_content_metadata(fsha) content_raw = service.lookup_content_raw(fsha) if content_raw: # FIXME: currently assuming utf8 encoding content['data'] = content_raw['data'] env['content'] = utils.prepare_data_for_view( content, encoding='utf-8') return render_template('content.html', **env) else: directory_files = api.api_directory(dir_or_file['target']) env['files'] = utils.prepare_data_for_view(directory_files) else: env['message'] = "Listing for directory %s:" % sha1_git directory_files = api.api_directory(sha1_git) env['files'] = utils.prepare_data_for_view(directory_files) except (NotFoundExc, BadInputExc) as e: env['message'] = str(e) return render_template('directory.html', **env) @app.route('/browse/origin//url//') @app.route('/browse/origin//') def browse_origin(origin_id=None, origin_type=None, origin_url=None): """Browse origin matching given criteria - either origin_id or origin_type and origin_path. Args: - origin_id: origin's swh identifier - origin_type: origin's type - origin_url: origin's URL """ # URLs for the calendar JS plugin env = {'browse_url': None, 'visit_url': None, 'origin': None} try: origin = api.api_origin(origin_id, origin_type, origin_url) env['origin'] = origin env['browse_url'] = url_for('browse_revision_with_origin', origin_id=origin['id']) env['visit_url'] = url_for('api_origin_visits', origin_id=origin['id']) except (NotFoundExc, BadInputExc) as e: env['message'] = str(e) return render_template('origin.html', **env) @app.route('/browse/person//') def browse_person(person_id): """Browse person with id id. """ env = {'person_id': person_id, 'person': None, 'message': None} try: env['person'] = api.api_person(person_id) except (NotFoundExc, BadInputExc) as e: env['message'] = str(e) return render_template('person.html', **env) @app.route('/browse/release//') def browse_release(sha1_git): """Browse release with sha1_git. """ env = {'sha1_git': sha1_git, 'message': None, 'release': None} try: rel = api.api_release(sha1_git) env['release'] = utils.prepare_data_for_view(rel) except (NotFoundExc, BadInputExc) as e: env['message'] = str(e) return render_template('release.html', **env) @app.route('/browse/revision//') @app.route('/browse/revision//prev//') def browse_revision(sha1_git, prev_sha1s=None): """Browse the revision with git SHA1 sha1_git_cur, while optionally keeping the context from which we came as a list of previous (i.e. later) revisions' sha1s. Args: sha1_git: the requested revision's sha1_git. prev_sha1s: an optional string of /-separated sha1s representing our context, ordered by descending revision date. Returns: Information about revision of git SHA1 sha1_git_cur, with relevant URLS pointing to the context augmented with sha1_git_cur. Example: GET /browse/revision/ """ env = {'sha1_git': sha1_git, 'message': None, 'revision': None} try: rev = api.api_revision(sha1_git, prev_sha1s) env['revision'] = utils.prepare_data_for_view(rev) except (NotFoundExc, BadInputExc) as e: env['message'] = str(e) return render_template('revision.html', **env) @app.route('/browse/revision//raw/') def browse_revision_raw_message(sha1_git): """Given a sha1_git, display the corresponding revision's raw message. """ return redirect(url_for('api_revision_raw_message', sha1_git=sha1_git)) @app.route('/browse/revision//log/') @app.route('/browse/revision//prev//log/') def browse_revision_log(sha1_git, prev_sha1s=None): """Browse revision with sha1_git's log. If the navigation path through the commit tree is specified, we intersect the earliest revision's log with the revisions the user browsed through - ie the path taken to the specified revision. Args: sha1_git: the current revision's SHA1_git checksum prev_sha1s: optionally, the path through which we want log information """ env = {'sha1_git': sha1_git, 'sha1_url': '/browse/revision/%s/' % sha1_git, 'message': None, 'revisions': []} try: revision_data = api.api_revision_log(sha1_git, prev_sha1s) revisions = revision_data['revisions'] next_revs_url = revision_data['next_revs_url'] env['revisions'] = map(utils.prepare_data_for_view, revisions) env['next_revs_url'] = utils.prepare_data_for_view(next_revs_url) except (NotFoundExc, BadInputExc) as e: env['message'] = str(e) return render_template('revision-log.html', **env) @app.route('/browse/revision' '/origin//log/') @app.route('/browse/revision' '/origin/' '/branch//log/') @app.route('/browse/revision' '/origin/' '/branch/' '/ts//log/') @app.route('/browse/revision' '/origin/' '/ts//log/') def browse_revision_log_by(origin_id, branch_name='refs/heads/master', timestamp=None): """Browse the revision described by origin, branch name and timestamp's log Args: origin_id: the revision's origin branch_name: the revision's branch timestamp: the requested timeframe for the revision Returns: The revision log of the described revision as a list of revisions if it is found. """ env = {'sha1_git': None, 'origin_id': origin_id, 'origin_url': '/browse/origin/%d/' % origin_id, 'branch_name': branch_name, 'timestamp': timestamp, 'message': None, 'revisions': []} try: revisions = api.api_revision_log_by( origin_id, branch_name, timestamp) env['revisions'] = map(utils.prepare_data_for_view, revisions) except (NotFoundExc, BadInputExc) as e: env['message'] = str(e) return render_template('revision-log.html', **env) @app.route('/browse/revision//prev//') def browse_with_rev_context(sha1_git_cur, sha1s): """Browse the revision with git SHA1 sha1_git_cur, while keeping the context from which we came as a list of previous (i.e. later) revisions' sha1s. Args: sha1_git_cur: the requested revision's sha1_git. sha1s: a string of /-separated sha1s representing our context, ordered by descending revision date. Returns: Information about revision of git SHA1 sha1_git_cur, with relevant URLS pointing to the context augmented with sha1_git_cur. Example: GET /browse/revision/ """ env = {'sha1_git': sha1_git_cur, 'message': None, 'revision': None} try: revision = api.api_revision( sha1_git_cur, sha1s) env['revision'] = utils.prepare_data_for_view(revision) except (BadInputExc, NotFoundExc) as e: env['message'] = str(e) return render_template('revision.html', **env) @app.route('/browse/revision//history//') def browse_revision_history(sha1_git_root, sha1_git): """Display information about revision sha1_git, limited to the sub-graph of all transitive parents of sha1_git_root. In other words, sha1_git is an ancestor of sha1_git_root. Args: sha1_git_root: latest revision of the browsed history. sha1_git: one of sha1_git_root's ancestors. limit: optional query parameter to limit the revisions log (default to 100). For now, note that this limit could impede the transitivity conclusion about sha1_git not being an ancestor of sha1_git_root (even if it is). Returns: Information on sha1_git if it is an ancestor of sha1_git_root including children leading to sha1_git_root. """ env = {'sha1_git_root': sha1_git_root, 'sha1_git': sha1_git, 'message': None, 'keys': [], 'revision': None} if sha1_git == sha1_git_root: return redirect(url_for('browse_revision', sha1_git=sha1_git)) try: revision = api.api_revision_history(sha1_git_root, sha1_git) env['revision'] = utils.prepare_data_for_view(revision) except (BadInputExc, NotFoundExc) as e: env['message'] = str(e) return render_template('revision.html', **env) @app.route('/browse/revision//directory/') @app.route('/browse/revision//directory//') def browse_revision_directory(sha1_git, dir_path=None): """Browse directory from revision with sha1_git. """ env = { 'sha1_git': sha1_git, 'path': '.' if not dir_path else dir_path, 'message': None, 'result': None } encoding = request.args.get('encoding', 'utf8') if encoding not in aliases: env['message'] = 'Encoding %s not supported.' \ 'Supported Encodings: %s' % ( encoding, list(aliases.keys())) return render_template('revision-directory.html', **env) try: result = api.api_revision_directory(sha1_git, dir_path, with_data=True) result['content'] = utils.prepare_data_for_view(result['content'], encoding=encoding) env['revision'] = result['revision'] env['result'] = result except (BadInputExc, NotFoundExc) as e: env['message'] = str(e) return render_template('revision-directory.html', **env) @app.route('/browse/revision/' '/history/' '/directory/') @app.route('/browse/revision/' '/history/' '/directory//') def browse_revision_history_directory(sha1_git_root, sha1_git, path=None): """Return information about directory pointed to by the revision defined as: revision sha1_git, limited to the sub-graph of all transitive parents of sha1_git_root. Args: sha1_git_root: latest revision of the browsed history. sha1_git: one of sha1_git_root's ancestors. path: optional directory pointed to by that revision. limit: optional query parameter to limit the revisions log (default to 100). For now, note that this limit could impede the transitivity conclusion about sha1_git not being an ancestor of sha1_git_root (even if it is). Returns: Information on the directory pointed to by that revision. Raises: BadInputExc in case of unknown algo_hash or bad hash. NotFoundExc if either revision is not found or if sha1_git is not an ancestor of sha1_git_root or the path referenced does not exist """ env = { 'sha1_git_root': sha1_git_root, 'sha1_git': sha1_git, 'path': '.' if not path else path, 'message': None, 'result': None } encoding = request.args.get('encoding', 'utf8') if encoding not in aliases: env['message'] = 'Encoding %s not supported.' \ 'Supported Encodings: %s' % ( encoding, list(aliases.keys())) return render_template('revision-directory.html', **env) if sha1_git == sha1_git_root: return redirect(url_for('browse_revision_directory', sha1_git=sha1_git, path=path, encoding=encoding), code=301) try: result = api.api_revision_history_directory(sha1_git_root, sha1_git, path, with_data=True) env['revision'] = result['revision'] env['content'] = utils.prepare_data_for_view(result['content'], encoding=encoding) env['result'] = result except (BadInputExc, NotFoundExc) as e: env['message'] = str(e) return render_template('revision-directory.html', **env) @app.route('/browse/revision' '/origin/' '/history/' '/directory/') @app.route('/browse/revision' '/origin/' '/history/' '/directory//') @app.route('/browse/revision' '/origin/' '/branch/' '/history/' '/directory/') @app.route('/browse/revision' '/origin/' '/branch/' '/history/' '/directory//') @app.route('/browse/revision' '/origin/' '/ts/' '/history/' '/directory/') @app.route('/browse/revision' '/origin/' '/ts/' '/history/' '/directory//') @app.route('/browse/revision' '/origin/' '/branch/' '/ts/' '/history/' '/directory/') @app.route('/browse/revision' '/origin/' '/branch/' '/ts/' '/history/' '/directory//') def browse_directory_through_revision_with_origin_history( origin_id, branch_name="refs/heads/master", ts=None, sha1_git=None, path=None): env = { 'origin_id': origin_id, 'branch_name': branch_name, 'ts': ts, 'sha1_git': sha1_git, 'path': '.' if not path else path, 'message': None, 'result': None } encoding = request.args.get('encoding', 'utf8') if encoding not in aliases: env['message'] = (('Encoding %s not supported.' 'Supported Encodings: %s') % ( encoding, list(aliases.keys()))) return render_template('revision-directory.html', **env) try: result = api.api_directory_through_revision_with_origin_history( origin_id, branch_name, ts, sha1_git, path, with_data=True) env['revision'] = result['revision'] env['content'] = utils.prepare_data_for_view(result['content'], encoding=encoding) env['result'] = result except (BadInputExc, NotFoundExc) as e: env['message'] = str(e) return render_template('revision-directory.html', **env) @app.route('/browse/revision' '/origin/') @app.route('/browse/revision' '/origin//') @app.route('/browse/revision' '/origin/' '/branch//') @app.route('/browse/revision' '/origin/' '/branch/' '/ts//') @app.route('/browse/revision' '/origin/' '/ts//') def browse_revision_with_origin(origin_id, branch_name="refs/heads/master", ts=None): """Instead of having to specify a (root) revision by SHA1_GIT, users might want to specify a place and a time. In SWH a "place" is an origin; a "time" is a timestamp at which some place has been observed by SWH crawlers. Args: origin_id: origin's identifier (default to 1). branch_name: the optional branch for the given origin (default to master). timestamp: optional timestamp (default to the nearest time crawl of timestamp). Returns: Information on the revision if found. Raises: BadInputExc in case of unknown algo_hash or bad hash. NotFoundExc if the revision is not found. """ env = {'message': None, 'revision': None} try: revision = api.api_revision_with_origin(origin_id, branch_name, ts) env['revision'] = utils.prepare_data_for_view(revision) except (ValueError, NotFoundExc, BadInputExc) as e: env['message'] = str(e) return render_template('revision.html', **env) @app.route('/browse/revision' '/origin/' '/history//') @app.route('/browse/revision' '/origin/' '/branch/' '/history//') @app.route('/browse/revision' '/origin/' '/branch/' '/ts/' '/history//') def browse_revision_history_through_origin(origin_id, branch_name='refs/heads/master', ts=None, sha1_git=None): """Return information about revision sha1_git, limited to the sub-graph of all transitive parents of the revision root identified by (origin_id, branch_name, ts). Given sha1_git_root such root revision's identifier, in other words, sha1_git is an ancestor of sha1_git_root. Args: origin_id: origin's identifier (default to 1). branch_name: the optional branch for the given origin (default to master). timestamp: optional timestamp (default to the nearest time crawl of timestamp). sha1_git: one of sha1_git_root's ancestors. limit: optional query parameter to limit the revisions log (default to 100). For now, note that this limit could impede the transitivity conclusion about sha1_git not being an ancestor of sha1_git_root (even if it is). Returns: Information on sha1_git if it is an ancestor of sha1_git_root including children leading to sha1_git_root. """ env = {'message': None, 'revision': None} try: revision = api.api_revision_history_through_origin( origin_id, branch_name, ts, sha1_git) env['revision'] = utils.prepare_data_for_view(revision) except (ValueError, BadInputExc, NotFoundExc) as e: env['message'] = str(e) return render_template('revision.html', **env) @app.route('/browse/revision' '/origin/' '/directory/') @app.route('/browse/revision' '/origin/' '/directory/') @app.route('/browse/revision' '/origin/' '/branch/' '/directory/') @app.route('/browse/revision' '/origin/' '/branch/' '/directory//') @app.route('/browse/revision' '/origin/' '/branch/' '/ts/' '/directory/') @app.route('/browse/revision' '/origin/' '/branch/' '/ts/' '/directory//') def browse_revision_directory_through_origin(origin_id, branch_name='refs/heads/master', ts=None, path=None): env = {'message': None, 'origin_id': origin_id, 'ts': ts, 'path': '.' if not path else path, 'result': None} encoding = request.args.get('encoding', 'utf8') if encoding not in aliases: env['message'] = 'Encoding %s not supported.' \ 'Supported Encodings: %s' % ( encoding, list(aliases.keys())) return render_template('revision-directory.html', **env) try: result = api.api_directory_through_revision_origin( origin_id, branch_name, ts, path, with_data=True) result['content'] = utils.prepare_data_for_view(result['content'], encoding=encoding) env['revision'] = result['revision'] env['result'] = result except (ValueError, BadInputExc, NotFoundExc) as e: env['message'] = str(e) return render_template('revision-directory.html', **env) @app.route('/browse/entity/') @app.route('/browse/entity//') def browse_entity(uuid): env = {'entities': [], 'message': None} try: entities = api.api_entity_by_uuid(uuid) env['entities'] = entities except (NotFoundExc, BadInputExc) as e: env['message'] = str(e) return render_template('entity.html', **env)