commit
7266c8018f
@ -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
|
||||||
|
|
||||||
|
@ -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;
|
|
||||||
|
@ -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
38
tests/helpers.py
Normal 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
31
tests/static/put.ics
Normal 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
11
tests/static/putvtodo.ics
Normal 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
1
tests/static/schema.sql
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../schema.sql
|
44
tests/test_auth.py
Normal file
44
tests/test_auth.py
Normal 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
|
@ -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), {}))
|
||||||
|
Loading…
Reference in New Issue
Block a user