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:
from dulwich.repo import Repo
GIT_REPOSITORY = Repo(os.path.join(FOLDER, ".git"))
GIT_REPOSITORY = Repo(FOLDER)
except:
GIT_REPOSITORY = None
@ -52,7 +52,7 @@ def open(path, mode="r"):
# On exit
if GIT_REPOSITORY and mode == "w":
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")
# pylint: enable=W0622

View File

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

View File

@ -21,25 +21,29 @@ Tests for Radicale.
"""
import base64
import hashlib
import os
import shutil
import sys
import tempfile
from dulwich.repo import Repo
from io import BytesIO
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
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):
"""Base class for tests."""
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):
def request(self, method, path, data=None, **args):
"""Send a request."""
self.application._status = None
self.application._headers = None
@ -49,14 +53,86 @@ class BaseTest(object):
args[key.upper()] = args[key]
args["REQUEST_METHOD"] = method.upper()
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)
return (
int(self.application._status.split()[0]),
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):
"""Put the response values into the current application."""
self.application._status = status
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."""
def test_root(self):
@ -32,3 +35,56 @@ class TestBaseRequests(BaseTest):
status, headers, answer = self.request("GET", "/")
assert status == 200
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), {}))