blob: 8b71b65135cc667aebcc90dd56e4730df6164041 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Tests for Apache(TM) Bloodhound's web modules"""
import sys
import unittest
from wsgiref.util import setup_testing_defaults
from trac.core import Component, implements
from trac.perm import PermissionCache, PermissionSystem
from trac.resource import ResourceNotFound
from trac.web.api import HTTPInternalError, HTTPNotFound, IRequestFilter, \
Request, RequestDone
from trac.web.href import Href
from trac.web.main import RequestDispatcher
from multiproduct.api import MultiProductSystem
from multiproduct.env import ProductEnvironment
from multiproduct.model import Product
from multiproduct.web_ui import ProductModule
from multiproduct.hooks import ProductRequestWithSession
from tests.env import MultiproductTestCase
#----------------
# Testing infrastructure for request handlers
#----------------
class TestRequestSpy(Component):
implements(IRequestFilter)
def testMatch(self, req, handler):
raise AssertionError('Test setup error: Missing match assertions')
def testProcessing(self, req, template, data, content_type):
raise AssertionError('Test setup error: Missing processing assertions')
# IRequestFilter methods
def pre_process_request(self, req, handler):
self.testMatch(req, handler)
return handler
def post_process_request(self, req, template, data, content_type):
self.testProcessing(req, template, data, content_type)
return template, data, content_type
class RequestHandlerTestCase(MultiproductTestCase):
"""Helper functions to write test cases for request handlers.
May be used as a mixin class.
"""
http_status = None
http_headers = None
http_body = None
record_response = False
def _get_request_obj(self, env):
environ = {}
setup_testing_defaults(environ)
environ['SCRIPT_NAME'] = env.href()
def start_response(status, headers):
if self.record_response:
self.http_status = status
self.http_headers = dict(headers)
self.http_body = []
return lambda body: self.http_body.append(body)
else:
return lambda body: None
req = ProductRequestWithSession(env, environ, start_response)
return req
def _dispatch(self, req, env):
req.perm = PermissionCache(env, req.authname)
return RequestDispatcher(env).dispatch(req)
def assertHttpHeaders(self, expectedHeaders):
for h, v in expectedHeaders.iteritems():
self.assertTrue(h in self.http_headers,
"Expected HTTP header '%s' not set" % (h,))
self.assertEquals(v, self.http_headers[h],
"Unexpected value for HTTP header '%s'" % (h,))
def assertRedirect(self, req, url, permanent=False):
if permanent:
self.assertEquals('301 Moved Permanently', self.http_status,
'Unexpected status code in HTTP redirect')
elif req.method == 'POST':
self.assertEquals('303 See Other', self.http_status,
'Unexpected status code in HTTP redirect')
else:
self.assertEquals('302 Found', self.http_status,
'Unexpected status code in HTTP redirect')
self.assertHttpHeaders({'Location' : url,
'Content-Type' : 'text/plain',
'Content-Length' : '0',
'Pragma' : 'no-cache',
'Cache-Control' : 'no-cache',
'Expires' : 'Fri, 01 Jan 1999 00:00:00 GMT'})
#----------------
# Testing product module
#----------------
class ProductModuleTestCase(RequestHandlerTestCase):
def setUp(self):
self._mp_setup()
self.global_env = self.env
self.env = ProductEnvironment(self.global_env, self.default_product)
self.global_env.enable_component(TestRequestSpy)
self.env.enable_component(TestRequestSpy)
TestRequestSpy(self.global_env).testMatch = self._assert_product_match
PermissionSystem(self.global_env).grant_permission('testuser', 'PRODUCT_CREATE')
PermissionSystem(self.global_env).grant_permission('testuser', 'PRODUCT_VIEW')
PermissionSystem(self.global_env).grant_permission('testuser', 'PRODUCT_MODIFY')
def tearDown(self):
self.global_env.reset_db()
self.env = self.global_env = None
expectedPrefix = None
expectedPathInfo = None
def _assert_product_match(self, req, handler):
self.assertIs(ProductModule(self.global_env), handler)
self.assertEqual(self.expectedPrefix, req.args['productid'],
"Unexpected product prefix")
self.assertEqual(self.expectedPathInfo, req.args['pathinfo'],
"Unexpected sub path")
def test_product_list(self):
spy = self.global_env[TestRequestSpy]
self.assertIsNot(None, spy)
req = self._get_request_obj(self.global_env)
req.authname = 'testuser'
req.environ['PATH_INFO'] = '/products'
mps = MultiProductSystem(self.global_env)
def assert_product_list(req, template, data, content_type):
self.assertEquals('product_list.html', template)
self.assertIs(None, content_type)
self.assertEquals([mps.default_product_prefix,
self.default_product],
[p.prefix for p in data.get('products')])
self.assertTrue('context' in data)
ctx = data['context']
self.assertEquals('product', ctx.resource.realm)
self.assertEquals(None, ctx.resource.id)
spy.testProcessing = assert_product_list
with self.assertRaises(RequestDone):
self._dispatch(req, self.global_env)
def test_product_new(self):
spy = self.global_env[TestRequestSpy]
self.assertIsNot(None, spy)
req = self._get_request_obj(self.global_env)
req.authname = 'testuser'
req.environ['PATH_INFO'] = '/products'
req.environ['QUERY_STRING'] = 'action=new'
def assert_product_new(req, template, data, content_type):
self.assertEquals('product_edit.html', template)
self.assertIs(None, content_type)
self.assertFalse('products' in data)
self.assertTrue('context' in data)
ctx = data['context']
self.assertEquals('product', ctx.resource.realm)
self.assertEquals(None, ctx.resource.id)
spy.testProcessing = assert_product_new
with self.assertRaises(RequestDone):
self._dispatch(req, self.global_env)
def test_product_view(self):
spy = self.global_env[TestRequestSpy]
self.assertIsNot(None, spy)
def assert_product_view(req, template, data, content_type):
self.assertEquals('product_view.html', template)
self.assertIs(None, content_type)
self.assertFalse('products' in data)
self.assertTrue('context' in data)
ctx = data['context']
self.assertEquals('product', ctx.resource.realm)
self.assertEquals(real_prefix, ctx.resource.id)
self.assertTrue('product' in data)
self.assertEquals(real_prefix, data['product'].prefix)
spy.testProcessing = assert_product_view
# Existing product
req = self._get_request_obj(self.global_env)
req.authname = 'testuser'
req.environ['PATH_INFO'] = '/products/%s' % (self.default_product,)
real_prefix = self.default_product
self.expectedPrefix = self.default_product
self.expectedPathInfo = ''
with self.assertRaises(RequestDone):
self._dispatch(req, self.global_env)
def test_missing_product(self):
spy = self.global_env[TestRequestSpy]
self.assertIsNot(None, spy)
mps = MultiProductSystem(self.global_env)
def assert_product_list(req, template, data, content_type):
self.assertEquals('product_list.html', template)
self.assertIs(None, content_type)
self.assertEquals([mps.default_product_prefix,
self.default_product],
[p.prefix for p in data.get('products')])
self.assertTrue('context' in data)
ctx = data['context']
self.assertEquals('product', ctx.resource.realm)
self.assertEquals(None, ctx.resource.id)
spy.testProcessing = assert_product_list
# Missing product
req = self._get_request_obj(self.global_env)
req.authname = 'testuser'
req.environ['PATH_INFO'] = '/products/missing'
self.expectedPrefix = 'missing'
self.expectedPathInfo = ''
with self.assertRaises(RequestDone):
self._dispatch(req, self.global_env)
self.assertEqual(1, len(req.chrome['warnings']))
self.assertEqual('Product missing not found',
req.chrome['warnings'][0].unescape())
def test_product_edit(self):
spy = self.global_env[TestRequestSpy]
self.assertIsNot(None, spy)
# HTTP GET
req = self._get_request_obj(self.global_env)
req.authname = 'testuser'
req.environ['PATH_INFO'] = '/products/%s' % (self.default_product,)
req.environ['QUERY_STRING'] = 'action=edit'
real_prefix = self.default_product
def assert_product_edit(req, template, data, content_type):
self.assertEquals('product_edit.html', template)
self.assertIs(None, content_type)
self.assertFalse('products' in data)
self.assertTrue('context' in data)
ctx = data['context']
self.assertEquals('product', ctx.resource.realm)
self.assertEquals(real_prefix, ctx.resource.id)
self.assertTrue('product' in data)
self.assertEquals(real_prefix, data['product'].prefix)
spy.testProcessing = assert_product_edit
self.expectedPrefix = self.default_product
self.expectedPathInfo = ''
with self.assertRaises(RequestDone):
self._dispatch(req, self.global_env)
# HTTP POST
req = self._get_request_obj(self.global_env)
req.authname = 'testuser'
req.environ['REQUEST_METHOD'] = 'POST'
req.environ['PATH_INFO'] = '/products/%s' % (self.default_product,)
req.args = dict(action='edit', description='New description',
prefix=self.default_product,
name=self.env.product.name)
spy.testProcessing = assert_product_edit
self.expectedPrefix = self.default_product
self.expectedPathInfo = ''
self.record_response = True
with self.assertRaises(RequestDone):
self._dispatch(req, self.global_env)
try:
product = Product(self.global_env,
{'prefix' : self.env.product.prefix})
except ResourceNotFound:
self.fail('Default test product deleted ?')
else:
self.assertEquals('New description', product.description)
product_url = Href(req.base_path).products(self.default_product)
self.assertRedirect(req, product_url)
def test_product_delete(self):
spy = self.global_env[TestRequestSpy]
self.assertIsNot(None, spy)
req = self._get_request_obj(self.global_env)
req.authname = 'testuser'
req.environ['PATH_INFO'] = '/products/%s' % (self.default_product,)
req.environ['QUERY_STRING'] = 'action=delete'
self.expectedPrefix = self.default_product
self.expectedPathInfo = ''
spy.testProcessing = lambda *args, **kwargs: None
with self.assertRaises(HTTPInternalError) as test_cm:
self._dispatch(req, self.global_env)
self.assertEqual('500 Trac Error (Product removal is not allowed!)',
unicode(test_cm.exception))
def test_suite():
return unittest.TestSuite([
unittest.makeSuite(ProductModuleTestCase,'test'),
])
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')