Преглед изворни кода

Merge pull request #61 from cristen/tests

Tests
Guillaume Ayoub пре 12 година
родитељ
комит
7266c8018f
9 измењених фајлова са 272 додато и 19 уклоњено
  1. 2 2
      radicale/storage/filesystem.py
  2. 1 5
      schema.sql
  3. 86 10
      tests/__init__.py
  4. 38 0
      tests/helpers.py
  5. 31 0
      tests/static/put.ics
  6. 11 0
      tests/static/putvtodo.ics
  7. 1 0
      tests/static/schema.sql
  8. 44 0
      tests/test_auth.py
  9. 58 2
      tests/test_base.py

+ 2 - 2
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
 

+ 1 - 5
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;

+ 86 - 10
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

+ 38 - 0
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 <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 - 0
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

+ 11 - 0
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

+ 1 - 0
tests/static/schema.sql

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

+ 44 - 0
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 <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

+ 58 - 2
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"<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), {}))