diff --git a/radicale/storage/filesystem.py b/radicale/storage/filesystem.py
index c6efa45..a08c0ff 100644
--- a/radicale/storage/filesystem.py
+++ b/radicale/storage/filesystem.py
@@ -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
diff --git a/schema.sql b/schema.sql
index 17619d5..c748e5f 100644
--- a/schema.sql
+++ b/schema.sql
@@ -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;
diff --git a/tests/__init__.py b/tests/__init__.py
index 6ef1413..ec5a84d 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -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
diff --git a/tests/helpers.py b/tests/helpers.py
new file mode 100644
index 0000000..b8b4c7d
--- /dev/null
+++ b/tests/helpers.py
@@ -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 .
+
+"""
+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)
diff --git a/tests/static/put.ics b/tests/static/put.ics
new file mode 100644
index 0000000..45f6a97
--- /dev/null
+++ b/tests/static/put.ics
@@ -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
diff --git a/tests/static/putvtodo.ics b/tests/static/putvtodo.ics
new file mode 100644
index 0000000..8c74fdb
--- /dev/null
+++ b/tests/static/putvtodo.ics
@@ -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
diff --git a/tests/static/schema.sql b/tests/static/schema.sql
new file mode 120000
index 0000000..50d2d72
--- /dev/null
+++ b/tests/static/schema.sql
@@ -0,0 +1 @@
+../../schema.sql
\ No newline at end of file
diff --git a/tests/test_auth.py b/tests/test_auth.py
new file mode 100644
index 0000000..4c5a550
--- /dev/null
+++ b/tests/test_auth.py
@@ -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 .
+
+"""
+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
diff --git a/tests/test_base.py b/tests/test_base.py
index dfc375a..542441b 100644
--- a/tests/test_base.py
+++ b/tests/test_base.py
@@ -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"%s" % 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), {}))