diff --git a/swh/web/ui/templates/home.html b/swh/web/ui/templates/home.html
index 558c14cdd..2e462bc62 100644
--- a/swh/web/ui/templates/home.html
+++ b/swh/web/ui/templates/home.html
@@ -1,9 +1,8 @@
{% extends "layout.html" %}
{% block title %}Home{% endblock %}
{% block content %}
{% endblock %}
diff --git a/swh/web/ui/templates/upload_and_search.html b/swh/web/ui/templates/upload_and_search.html
index 8bd9832c4..30a94891c 100644
--- a/swh/web/ui/templates/upload_and_search.html
+++ b/swh/web/ui/templates/upload_and_search.html
@@ -1,21 +1,34 @@
{% extends "layout.html" %}
-{% block title %}Upload, hash and search{% endblock %}
+{% block title %}Search or Upload/Hash/Search{% endblock %}
{% block content %}
-{% if message is not none %}
-
+
+ Or upload some file, we'll hash and search it:
+
+ {% if message is not none %}
+
+
{{ message | safe }}
+ {% endif %}
+
-{{ message | safe }}
-{% endif %}
{% endblock %}
diff --git a/swh/web/ui/tests/test_views.py b/swh/web/ui/tests/test_views.py
index cb5e92e60..f44098611 100644
--- a/swh/web/ui/tests/test_views.py
+++ b/swh/web/ui/tests/test_views.py
@@ -1,388 +1,381 @@
# 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 swh.web.ui.tests import test_app
from unittest.mock import patch, MagicMock
from swh.web.ui.exc import BadInputExc
class ViewTestCase(test_app.SWHViewTestCase):
render_template = False
@istest
def info(self):
# when
rv = self.client.get('/about')
self.assertEquals(rv.status_code, 200)
self.assert_template_used('about.html')
self.assertIn(b'About', rv.data)
@istest
def search_default(self):
# when
rv = self.client.get('/search')
self.assertEquals(rv.status_code, 200)
self.assertEqual(self.get_context_variable('q'), '')
self.assertEqual(self.get_context_variable('message'), '')
- self.assert_template_used('search.html')
+ self.assertEqual(self.get_context_variable('filename'), None)
+ self.assertEqual(self.get_context_variable('found'), None)
+ self.assert_template_used('upload_and_search.html')
@patch('swh.web.ui.views.service')
@istest
def search_content_found(self, mock_service):
# given
mock_service.lookup_hash.return_value = {
'found': True,
'algo': 'sha1'
}
# when
rv = self.client.get('/search?q=sha1:123')
self.assertEquals(rv.status_code, 200)
- self.assert_template_used('search.html')
+ self.assert_template_used('upload_and_search.html')
self.assertEqual(self.get_context_variable('q'), 'sha1:123')
self.assertEqual(self.get_context_variable('message'),
'Content with hash sha1:123 found!')
mock_service.lookup_hash.assert_called_once_with('sha1:123')
@patch('swh.web.ui.views.service')
@istest
def search_content_not_found(self, mock_service):
# given
mock_service.lookup_hash.return_value = {
'found': False,
'algo': 'sha1'
}
# when
rv = self.client.get('/search?q=sha1:456')
self.assertEquals(rv.status_code, 200)
- self.assert_template_used('search.html')
+ self.assert_template_used('upload_and_search.html')
self.assertEqual(self.get_context_variable('q'), 'sha1:456')
self.assertEqual(self.get_context_variable('message'),
'Content with hash sha1:456 not found!')
mock_service.lookup_hash.assert_called_once_with('sha1:456')
@patch('swh.web.ui.views.service')
@istest
def search_content_invalid_query(self, mock_service):
# given
mock_service.lookup_hash = MagicMock(
side_effect=BadInputExc('Invalid query!')
)
# when
rv = self.client.get('/search?q=sha1:invalid-hash')
self.assertEquals(rv.status_code, 200)
- self.assert_template_used('search.html')
+ self.assert_template_used('upload_and_search.html')
self.assertEqual(self.get_context_variable('q'), 'sha1:invalid-hash')
self.assertEqual(self.get_context_variable('message'),
'Invalid query!')
mock_service.lookup_hash.assert_called_once_with('sha1:invalid-hash')
+ @patch('swh.web.ui.views.service')
+ @patch('swh.web.ui.views.request')
+ @istest
+ def search_post_not_found(self, mock_request, mock_service):
+ # given
+ mock_request.args = {}
+ mock_request.method = 'POST'
+ mock_request.files = dict(filename='foobar')
+ mock_service.upload_and_search.return_value = {'filename': 'foobar',
+ 'sha1': 'blahhash',
+ 'found': False}
+
+ # when
+ # the mock mock_request completes the post request
+ rv = self.client.post('/search')
+
+ # then
+ self.assertEquals(rv.status_code, 200)
+ self.assertEqual(self.get_context_variable('message'),
+ 'The file foobar with hash blahhash has not been'
+ ' found.')
+ self.assertEqual(self.get_context_variable('filename'), 'foobar')
+ self.assertEqual(self.get_context_variable('found'), False)
+ self.assertEqual(self.get_context_variable('sha1'), 'blahhash')
+ self.assert_template_used('upload_and_search.html')
+
+ mock_service.upload_and_search.called = True
+
+ @patch('swh.web.ui.views.service')
+ @patch('swh.web.ui.views.request')
+ @istest
+ def search_post_found(self, mock_request, mock_service):
+ # given
+ mock_request.args = {}
+ mock_request.method = 'POST'
+ mock_request.files = dict(filename='foobar')
+ mock_service.upload_and_search.return_value = {'filename': 'foobar',
+ 'sha1': 'hash-blah',
+ 'found': True}
+
+ # when
+ # the mock mock_request completes the post request
+ rv = self.client.post('/search')
+
+ # then
+ self.assertEquals(rv.status_code, 200)
+ self.assertEqual(self.get_context_variable('message'),
+ 'The file foobar with hash hash-blah has been found.')
+ self.assertEqual(self.get_context_variable('filename'), 'foobar')
+ self.assertEqual(self.get_context_variable('found'), True)
+ self.assertEqual(self.get_context_variable('sha1'), 'hash-blah')
+ self.assert_template_used('upload_and_search.html')
+
+ mock_service.upload_and_search.called = True
+
+ @patch('swh.web.ui.views.service')
+ @patch('swh.web.ui.views.request')
+ @istest
+ def search_post_bad_input(self, mock_request, mock_service):
+ # given
+ mock_request.method = 'POST'
+ mock_request.args = {}
+ mock_request.files = dict(filename='foobar')
+ mock_service.upload_and_search.side_effect = BadInputExc(
+ 'Invalid hash')
+
+ # when
+ # the mock mock_request completes the post request
+ rv = self.client.post('/search')
+
+ # then
+ self.assertEquals(rv.status_code, 200)
+ self.assertEqual(self.get_context_variable('message'), 'Invalid hash')
+ self.assert_template_used('upload_and_search.html')
+
+ mock_service.upload_and_search.called = True
+
@patch('swh.web.ui.views.service')
@istest
def show_content(self, mock_service):
# given
stub_content_raw = {
'sha1': 'sha1-hash',
'data': b'some-data'
}
mock_service.lookup_content_raw.return_value = stub_content_raw
# when
rv = self.client.get('/browse/content/sha1:sha1-hash/raw')
self.assertEquals(rv.status_code, 200)
self.assert_template_used('display_content.html')
self.assertEqual(self.get_context_variable('message'),
'Content sha1-hash')
self.assertEqual(self.get_context_variable('content'),
stub_content_raw)
mock_service.lookup_content_raw.assert_called_once_with(
'sha1:sha1-hash')
@patch('swh.web.ui.views.service')
@istest
def show_content_not_found(self, mock_service):
# given
mock_service.lookup_content_raw.return_value = None
# when
rv = self.client.get('/browse/content/sha1:sha1-unknown/raw')
self.assertEquals(rv.status_code, 200)
self.assert_template_used('display_content.html')
self.assertEqual(self.get_context_variable('message'),
'Content with sha1:sha1-unknown not found.')
self.assertEqual(self.get_context_variable('content'), None)
mock_service.lookup_content_raw.assert_called_once_with(
'sha1:sha1-unknown')
@patch('swh.web.ui.views.service')
@istest
def show_content_invalid_hash(self, mock_service):
# given
mock_service.lookup_content_raw.side_effect = BadInputExc(
'Invalid hash')
# when
rv = self.client.get('/browse/content/sha2:sha1-invalid/raw')
self.assertEquals(rv.status_code, 200)
self.assert_template_used('display_content.html')
self.assertEqual(self.get_context_variable('message'),
'Invalid hash')
self.assertEqual(self.get_context_variable('content'), None)
mock_service.lookup_content_raw.assert_called_once_with(
'sha2:sha1-invalid')
@patch('swh.web.ui.views.service')
@patch('swh.web.ui.utils')
@istest
def browse_directory_bad_input(self, mock_utils, mock_service):
# given
mock_service.lookup_directory.side_effect = BadInputExc('Invalid hash')
# when
rv = self.client.get('/browse/directory/sha2-invalid')
# then
self.assertEquals(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_service.lookup_directory.assert_called_once_with(
'sha2-invalid')
@patch('swh.web.ui.views.service')
@patch('swh.web.ui.utils')
@istest
def browse_directory_empty_result(self, mock_utils, mock_service):
# given
mock_service.lookup_directory.return_value = None
# when
rv = self.client.get('/browse/directory/some-sha1')
# then
self.assertEquals(rv.status_code, 200)
self.assert_template_used('directory.html')
self.assertEqual(self.get_context_variable('message'),
'Directory some-sha1 not found.')
self.assertEqual(self.get_context_variable('files'), [])
mock_service.lookup_directory.assert_called_once_with(
'some-sha1')
@patch('swh.web.ui.views.service')
@patch('swh.web.ui.views.utils')
@istest
def browse_directory(self, mock_utils, mock_service):
# 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_service.lookup_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_directory_listing.return_value = stub_directory_map
# when
rv = self.client.get('/browse/directory/some-sha1')
# then
print(self.templates)
self.assertEquals(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_service.lookup_directory.assert_called_once_with(
'some-sha1')
mock_utils.prepare_directory_listing.assert_called_once_with(
stub_directory_ls)
@patch('swh.web.ui.views.service')
@istest
def content_with_origin_content_not_found(self, mock_service):
# given
mock_service.lookup_hash.return_value = {'found': False}
# when
rv = self.client.get('/browse/content/sha256:some-sha256')
# then
self.assertEquals(rv.status_code, 200)
self.assert_template_used('content.html')
self.assertEqual(self.get_context_variable('message'),
'Hash sha256:some-sha256 was not found.')
mock_service.lookup_hash.assert_called_once_with(
'sha256:some-sha256')
mock_service.lookup_hash_origin.called = False
@patch('swh.web.ui.views.service')
@istest
def content_with_origin_bad_input(self, mock_service):
# given
mock_service.lookup_hash.side_effect = BadInputExc('Invalid hash')
# when
rv = self.client.get('/browse/content/sha256:some-sha256')
# then
self.assertEquals(rv.status_code, 200)
self.assert_template_used('content.html')
self.assertEqual(
self.get_context_variable('message'), 'Invalid hash')
mock_service.lookup_hash.assert_called_once_with(
'sha256:some-sha256')
mock_service.lookup_hash_origin.called = False
@patch('swh.web.ui.views.service')
@istest
def content_with_origin(self, mock_service):
# given
mock_service.lookup_hash.return_value = {'found': True}
mock_service.lookup_hash_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')
# then
self.assertEquals(rv.status_code, 200)
self.assert_template_used('content.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_service.lookup_hash.assert_called_once_with(
'sha256:some-sha256')
mock_service.lookup_hash_origin.assert_called_once_with(
'sha256:some-sha256')
-
- @istest
- def uploadnsearch_get(self):
- # when
- rv = self.client.get('/uploadnsearch')
-
- # then
- self.assertEquals(rv.status_code, 200)
- self.assertEqual(self.get_context_variable('message'), '')
- self.assertEqual(self.get_context_variable('filename'), None)
- self.assertEqual(self.get_context_variable('found'), None)
- self.assert_template_used('upload_and_search.html')
-
- @patch('swh.web.ui.views.service')
- @patch('swh.web.ui.views.request')
- @istest
- def uploadnsearch_post_not_found(self, mock_request, mock_service):
- # given
- mock_request.method = 'POST'
- mock_request.files = dict(filename='foobar')
- mock_service.upload_and_search.return_value = {'filename': 'foobar',
- 'sha1': 'blahhash',
- 'found': False}
-
- # when
- # the mock mock_request completes the post request
- rv = self.client.post('/uploadnsearch')
-
- # then
- self.assertEquals(rv.status_code, 200)
- self.assertEqual(self.get_context_variable('message'),
- 'The file foobar with hash blahhash has not been'
- ' found.')
- self.assertEqual(self.get_context_variable('filename'), 'foobar')
- self.assertEqual(self.get_context_variable('found'), False)
- self.assertEqual(self.get_context_variable('sha1'), 'blahhash')
- self.assert_template_used('upload_and_search.html')
-
- mock_service.upload_and_search.called = True
-
- @patch('swh.web.ui.views.service')
- @patch('swh.web.ui.views.request')
- @istest
- def uploadnsearch_post_found(self, mock_request, mock_service):
- # given
- mock_request.method = 'POST'
- mock_request.files = dict(filename='foobar')
- mock_service.upload_and_search.return_value = {'filename': 'foobar',
- 'sha1': 'hash-blah',
- 'found': True}
-
- # when
- # the mock mock_request completes the post request
- rv = self.client.post('/uploadnsearch')
-
- # then
- self.assertEquals(rv.status_code, 200)
- self.assertEqual(self.get_context_variable('message'),
- 'The file foobar with hash hash-blah has been found.')
- self.assertEqual(self.get_context_variable('filename'), 'foobar')
- self.assertEqual(self.get_context_variable('found'), True)
- self.assertEqual(self.get_context_variable('sha1'), 'hash-blah')
- self.assert_template_used('upload_and_search.html')
-
- mock_service.upload_and_search.called = True
-
- @patch('swh.web.ui.views.service')
- @patch('swh.web.ui.views.request')
- @istest
- def uploadnsearch_post_bad_input(self, mock_request, mock_service):
- # given
- mock_request.method = 'POST'
- mock_request.files = dict(filename='foobar')
- mock_service.upload_and_search.side_effect = BadInputExc(
- 'Invalid hash')
-
- # when
- # the mock mock_request completes the post request
- rv = self.client.post('/uploadnsearch')
-
- # then
- self.assertEquals(rv.status_code, 200)
- self.assertEqual(self.get_context_variable('message'), 'Invalid hash')
- self.assert_template_used('upload_and_search.html')
-
- mock_service.upload_and_search.called = True
diff --git a/swh/web/ui/views.py b/swh/web/ui/views.py
index 547649300..e0829f9f8 100644
--- a/swh/web/ui/views.py
+++ b/swh/web/ui/views.py
@@ -1,218 +1,211 @@
# 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 flask import render_template, flash, request
from flask.ext.api.decorators import set_renderers
from flask.ext.api.renderers import HTMLRenderer
from swh.core.hashutil import ALGORITHMS
from swh.web.ui import service, utils
from swh.web.ui.exc import BadInputExc
from swh.web.ui.main import app
hash_filter_keys = ALGORITHMS
@app.route('/')
@set_renderers(HTMLRenderer)
def homepage():
"""Home page
"""
flash('This Web app is still work in progress, use at your own risk',
'warning')
# return redirect(url_for('about'))
return render_template('home.html')
@app.route('/about')
@set_renderers(HTMLRenderer)
def about():
return render_template('about.html')
-@app.route('/search')
+@app.route('/search', methods=['GET', 'POST'])
@set_renderers(HTMLRenderer)
def search():
"""Search for hashes in swh-storage.
"""
- q = request.args.get('q', '')
- env = {'q': q, 'message': ''}
-
- try:
- if q:
- r = service.lookup_hash(q)
- env['message'] = 'Content with hash %s%sfound!' % (
- q,
- ' ' if r['found'] == True else ' not '
- )
- except BadInputExc as e:
- env['message'] = str(e)
-
- return render_template('search.html', **env)
-
+ env = {'filename': None, 'message': '', 'found': None, 'q': ''}
-@app.route('/uploadnsearch', methods=['GET', 'POST'])
-@set_renderers(HTMLRenderer)
-def uploadnsearch():
- """Upload and search for hashes in swh-storage.
+ q = request.args.get('q', '')
+ if q:
+ env = {'q': q, 'message': ''}
- """
- env = {'filename': None, 'message': '', 'found': None}
+ try:
+ if q:
+ r = service.lookup_hash(q)
+ env['message'] = 'Content with hash %s%sfound!' % (
+ q,
+ ' ' if r['found'] == True else ' not '
+ )
+ env['q'] = q
+ except BadInputExc as e:
+ env['message'] = str(e)
- if request.method == 'POST':
+ elif request.method == 'POST':
file = request.files['filename']
try:
uploaded_content = service.upload_and_search(file)
filename = uploaded_content['filename']
sha1 = uploaded_content['sha1']
found = uploaded_content['found']
message = 'The file %s with hash %s has%sbeen found.' % (
filename,
sha1,
' ' if found else ' not ')
env.update({
'filename': filename,
'sha1': sha1,
'found': found,
'message': message
})
except BadInputExc as e:
env['message'] = str(e)
return render_template('upload_and_search.html', **env)
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/')
@set_renderers(HTMLRenderer)
def 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:
content = service.lookup_hash(q)
if not content.get('found'):
message = "Hash %s was not found." % q
else:
origin = service.lookup_hash_origin(q)
message = _origin_seen(q, origin)
except BadInputExc as e: # do not like it but do not duplicate code
message = str(e)
env['message'] = message
return render_template('content.html', **env)
@app.route('/browse/content//raw')
@set_renderers(HTMLRenderer)
def show_content(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.
"""
env = {}
try:
content = service.lookup_content_raw(q)
if content:
# FIXME: will break if not utf-8
content['data'] = content['data'].decode('utf-8')
message = 'Content %s' % content['sha1']
else:
message = 'Content with %s not found.' % q
except BadInputExc as e:
message = str(e)
content = None
env['message'] = message
env['content'] = content
return render_template('display_content.html', **env)
@app.route('/browse/directory/')
@set_renderers(HTMLRenderer)
def browse_directory(sha1_git):
"""Show directory information.
Args:
- sha1_git: the directory's sha1 git identifier.
Returns:
The content's information at sha1_git
"""
env = {'sha1_git': sha1_git}
try:
directory_files = service.lookup_directory(sha1_git)
if directory_files:
message = "Listing for directory %s:" % sha1_git
files = utils.prepare_directory_listing(directory_files)
else:
message = "Directory %s not found." % sha1_git
files = []
except BadInputExc as e: # do not like it but do not duplicate code
message = str(e)
files = []
env['message'] = message
env['files'] = files
return render_template('directory.html', **env)