Merge pull request #61 from cristen/tests

Tests
This commit is contained in:
Guillaume Ayoub 2013-09-06 07:13:17 -07:00
commit 7266c8018f
9 changed files with 272 additions and 19 deletions

View File

@ -35,7 +35,7 @@ FOLDER = os.path.expanduser(config.get("storage", "filesystem_folder"))
try: try:
from dulwich.repo import Repo from dulwich.repo import Repo
GIT_REPOSITORY = Repo(os.path.join(FOLDER, ".git")) GIT_REPOSITORY = Repo(FOLDER)
except: except:
GIT_REPOSITORY = None GIT_REPOSITORY = None
@ -52,7 +52,7 @@ def open(path, mode="r"):
# On exit # On exit
if GIT_REPOSITORY and mode == "w": if GIT_REPOSITORY and mode == "w":
path = os.path.relpath(abs_path, FOLDER) path = os.path.relpath(abs_path, FOLDER)
GIT_REPOSITORY.stage([path]) GIT_REPOSITORY.stage([path.encode("utf-8")])
GIT_REPOSITORY.do_commit("Commit by Radicale") GIT_REPOSITORY.do_commit("Commit by Radicale")
# pylint: enable=W0622 # pylint: enable=W0622

View File

@ -1,7 +1,5 @@
-- This is the database schema for PostgreSQL. -- This is the database schema for PostgreSQL.
begin;
create table collection ( create table collection (
path varchar primary key not null, path varchar primary key not null,
parent_path varchar references collection (path)); parent_path varchar references collection (path));
@ -22,12 +20,10 @@ create table line (
value varchar not null, value varchar not null,
item_name varchar references item (name) not null, item_name varchar references item (name) not null,
timestamp timestamp not null, timestamp timestamp not null,
primary key (key, item_name)); primary key (key, value, item_name, timestamp));
create table property ( create table property (
key varchar not null, key varchar not null,
value varchar not null, value varchar not null,
collection_path varchar references collection (path) not null, collection_path varchar references collection (path) not null,
primary key (key, collection_path)); primary key (key, collection_path));
commit;

View File

@ -21,25 +21,29 @@ Tests for Radicale.
""" """
import base64
import hashlib
import os import os
import shutil
import sys import sys
import tempfile
from dulwich.repo import Repo
from io import BytesIO
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
import radicale import radicale
from radicale import config
from radicale.auth import htpasswd
from radicale.storage import filesystem, database
from .helpers import get_file_content
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
class BaseTest(object): class BaseTest(object):
"""Base class for tests.""" """Base class for tests."""
def request(self, method, path, data=None, **args):
def setup(self):
"""Setup function for each test."""
self.application = radicale.Application()
def teardown(self):
"""Teardown function for each test."""
def request(self, method, path, **args):
"""Send a request.""" """Send a request."""
self.application._status = None self.application._status = None
self.application._headers = None self.application._headers = None
@ -49,14 +53,86 @@ class BaseTest(object):
args[key.upper()] = args[key] args[key.upper()] = args[key]
args["REQUEST_METHOD"] = method.upper() args["REQUEST_METHOD"] = method.upper()
args["PATH_INFO"] = path args["PATH_INFO"] = path
if data:
args["wsgi.input"] = BytesIO(data)
args["CONTENT_LENGTH"] = str(len(data))
self.application._answer = self.application(args, self.start_response) self.application._answer = self.application(args, self.start_response)
return ( return (
int(self.application._status.split()[0]), int(self.application._status.split()[0]),
dict(self.application._headers), dict(self.application._headers),
self.application._answer[0].decode("utf-8")) self.application._answer[0].decode("utf-8")
if self.application._answer else None)
def start_response(self, status, headers): def start_response(self, status, headers):
"""Put the response values into the current application.""" """Put the response values into the current application."""
self.application._status = status self.application._status = status
self.application._headers = headers self.application._headers = headers
class FileSystem(BaseTest):
"""Base class for filesystem tests."""
storage_type = "filesystem"
def setup(self):
"""Setup function for each test."""
self.colpath = tempfile.mkdtemp()
config.set("storage", "type", self.storage_type)
filesystem.FOLDER = self.colpath
filesystem.GIT_REPOSITORY = None
self.application = radicale.Application()
def teardown(self):
"""Teardown function for each test."""
shutil.rmtree(self.colpath)
class MultiFileSystem(FileSystem):
"""Base class for multifilesystem tests."""
storage_type = "multifilesystem"
class DataBaseSystem(BaseTest):
"""Base class for database tests"""
def setup(self):
config.set("storage", "type", "database")
config.set("storage", "database_url", "sqlite://")
database.Session = sessionmaker()
database.Session.configure(bind=create_engine("sqlite://"))
session = database.Session()
# session.execute(get_file_content("schema.sql"))
for st in get_file_content("schema.sql").split(";"):
session.execute(st)
session.commit()
self.application = radicale.Application()
class GitFileSystem(FileSystem):
"""Base class for filesystem tests using Git"""
def setup(self):
super(GitFileSystem, self).setup()
Repo.init(self.colpath)
filesystem.GIT_REPOSITORY = Repo(self.colpath)
class GitMultiFileSystem(GitFileSystem, MultiFileSystem):
"""Base class for multifilesystem tests using Git"""
class HtpasswdAuthSystem(BaseTest):
"""Base class to test Radicale with Htpasswd authentication"""
def setup(self):
self.colpath = tempfile.mkdtemp()
htpasswd_file_path = os.path.join(self.colpath, ".htpasswd")
with open(htpasswd_file_path, "w") as fd:
fd.write('tmp:{SHA}' + base64.b64encode(
hashlib.sha1("bépo").digest()))
config.set("auth", "type", "htpasswd")
self.userpass = base64.b64encode("tmp:bépo")
self.application = radicale.Application()
htpasswd.FILENAME = htpasswd_file_path
htpasswd.ENCRYPTION = "sha1"
def teardown(self):
config.set("auth", "type", "None")
radicale.auth.is_authenticated = lambda *_: True

38
tests/helpers.py Normal file
View File

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
#
# This file is part of Radicale Server - Calendar Server
# Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter
# Copyright © 2008-2013 Guillaume Ayoub
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
"""
Radicale Helpers module.
This module offers helpers to use in tests.
"""
import os
EXAMPLES_FOLDER = os.path.join(os.path.dirname(__file__), "static")
def get_file_content(file_name):
try:
with open(os.path.join(EXAMPLES_FOLDER, file_name)) as fd:
return fd.read()
except IOError:
print(u"Couldn't open the file %s" % file_name)

31
tests/static/put.ics Normal file
View File

@ -0,0 +1,31 @@
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Paris
X-LIC-LOCATION:Europe/Paris
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20130902T150157Z
LAST-MODIFIED:20130902T150158Z
DTSTAMP:20130902T150158Z
UID:02805f81-4cc2-4d68-8d39-72768ffa02d9
SUMMARY:Nouvel évènement
DTSTART;TZID=Europe/Paris:20130902T180000
DTEND;TZID=Europe/Paris:20130902T190000
END:VEVENT
END:VCALENDAR

11
tests/static/putvtodo.ics Normal file
View File

@ -0,0 +1,11 @@
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTODO
CREATED:20130903T091105Z
LAST-MODIFIED:20130903T091108Z
DTSTAMP:20130903T091108Z
UID:40f8cf9b-0e62-4624-89a2-24c5e68850f5
SUMMARY:Nouvelle tâche
END:VTODO
END:VCALENDAR

1
tests/static/schema.sql Symbolic link
View File

@ -0,0 +1 @@
../../schema.sql

44
tests/test_auth.py Normal file
View File

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
#
# This file is part of Radicale Server - Calendar Server
# Copyright © 2012-2013 Guillaume Ayoub
# Copyright © 2012-2013 Jean-Marc Martins
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
"""
Radicale tests with simple requests and authentication.
"""
from nose import with_setup
from . import HtpasswdAuthSystem
class TestBaseAuthRequests(HtpasswdAuthSystem):
"""
Tests basic requests with auth.
..note Only htpasswd works at the moment since
it requires to spawn processes running servers for
others auth methods (ldap).
"""
@with_setup(HtpasswdAuthSystem.setup, HtpasswdAuthSystem.teardown)
def test_root(self):
"""Tests a GET request at "/"."""
status, headers, answer = self.request(
"GET", "/", HTTP_AUTHORIZATION=self.userpass)
assert status == 200
assert "Radicale works!" in answer

View File

@ -21,10 +21,13 @@ Radicale tests with simple requests.
""" """
from . import BaseTest from . import (FileSystem, MultiFileSystem, DataBaseSystem,
GitFileSystem, GitMultiFileSystem)
from .helpers import get_file_content
import sys
class TestBaseRequests(BaseTest): class BaseRequests(object):
"""Tests with simple requests.""" """Tests with simple requests."""
def test_root(self): def test_root(self):
@ -32,3 +35,56 @@ class TestBaseRequests(BaseTest):
status, headers, answer = self.request("GET", "/") status, headers, answer = self.request("GET", "/")
assert status == 200 assert status == 200
assert "Radicale works!" in answer assert "Radicale works!" in answer
# Tests the creation of the collection
status, headers, answer = self.request("GET", "/calendar.ics/")
assert u"BEGIN:VCALENDAR" in answer
assert u"VERSION:2.0" in answer
assert u"END:VCALENDAR" in answer
assert u"PRODID:-//Radicale//NONSGML Radicale Server//EN" in answer
def test_add_event_todo(self):
"""Tests the add of an event and todo."""
self.request("GET", "/calendar.ics/")
#VEVENT test
event = get_file_content("put.ics")
path = "/calendar.ics/02805f81-4cc2-4d68-8d39-72768ffa02d9.ics"
status, headers, answer = self.request("PUT", path, event)
assert status == 201
assert u"ETag" in headers.keys()
status, headers, answer = self.request("GET", path)
assert status == 200
assert u"VEVENT" in answer
assert u"Nouvel évènement" in answer
assert u"UID:02805f81-4cc2-4d68-8d39-72768ffa02d9" in answer
# VTODO test
todo = get_file_content("putvtodo.ics")
path = "/calendar.ics/40f8cf9b-0e62-4624-89a2-24c5e68850f5.ics"
status, headers, answer = self.request("PUT", path, todo)
assert status == 201
assert u"ETag" in headers.keys()
status, headers, answer = self.request("GET", path)
assert u"VTODO" in answer
assert u"Nouvelle tâche" in answer
assert u"UID:40f8cf9b-0e62-4624-89a2-24c5e68850f5" in answer
def test_delete(self):
"""Tests the deletion of an event"""
self.request("GET", "/calendar.ics/")
# Adds a VEVENT to be deleted
event = get_file_content("put.ics")
path = "/calendar.ics/02805f81-4cc2-4d68-8d39-72768ffa02d9.ics"
status, headers, answer = self.request("PUT", path, event)
# Then we send a DELETE request
status, headers, answer = self.request("DELETE", path)
assert status == 200
assert u"<href>%s</href>" % path in answer
status, headers, answer = self.request("GET", "/calendar.ics/")
assert u"VEVENT" not in answer
# Generates Classes with different configs
cl_list = [FileSystem, MultiFileSystem, DataBaseSystem,
GitFileSystem, GitMultiFileSystem]
for cl in cl_list:
classname = "Test%s" % cl.__name__
setattr(sys.modules[__name__],
classname, type(classname, (BaseRequests, cl), {}))