0001-add-tests-13612.patch
get_wcs.sh | ||
---|---|---|
1 |
#!/bin/sh -xue |
|
2 | ||
3 |
test -d wcs || git clone http://git.entrouvert.org/wcs.git |
|
4 |
(cd wcs && git pull) |
tests/conftest.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 | ||
3 |
import sys |
|
4 |
import subprocess |
|
5 |
import time |
|
6 |
import os |
|
7 |
import shutil |
|
8 |
import random |
|
9 |
import socket |
|
10 |
from contextlib import closing |
|
11 |
from collections import namedtuple |
|
12 | ||
13 |
import psycopg2 |
|
14 | ||
15 |
import pytest |
|
16 | ||
17 |
Wcs = namedtuple('Wcs', ['url', 'appdir', 'pid']) |
|
18 | ||
19 | ||
20 |
class Database(object): |
|
21 |
def __init__(self): |
|
22 |
self.db_name = 'db%s' % random.getrandbits(20) |
|
23 |
self.dsn = 'dbname=%s' % self.db_name |
|
24 |
with closing(psycopg2.connect('')) as conn: |
|
25 |
conn.set_isolation_level(0) |
|
26 |
with conn.cursor() as cursor: |
|
27 |
cursor.execute('CREATE DATABASE %s' % self.db_name) |
|
28 | ||
29 |
def conn(self): |
|
30 |
return closing(psycopg2.connect(self.dsn)) |
|
31 | ||
32 |
def delete(self): |
|
33 |
with closing(psycopg2.connect('')) as conn: |
|
34 |
conn.set_isolation_level(0) |
|
35 |
with conn.cursor() as cursor: |
|
36 |
cursor.execute('DROP DATABASE IF EXISTS %s' % self.db_name) |
|
37 | ||
38 | ||
39 |
@pytest.fixture |
|
40 |
def postgres_db(): |
|
41 |
db = Database() |
|
42 |
try: |
|
43 |
yield db |
|
44 |
finally: |
|
45 |
db.delete() |
|
46 | ||
47 | ||
48 |
WCS_SCRIPTS = { |
|
49 |
'setup-auth': u""" |
|
50 |
from quixote import get_publisher |
|
51 | ||
52 |
get_publisher().cfg['identification'] = {'methods': ['password']} |
|
53 |
get_publisher().cfg['debug'] = {'display_exceptions': 'text'} |
|
54 |
get_publisher().write_cfg() |
|
55 |
""", |
|
56 |
'create-user': u""" |
|
57 |
from quixote import get_publisher |
|
58 |
from qommon.ident.password_accounts import PasswordAccount |
|
59 | ||
60 |
user = get_publisher().user_class() |
|
61 |
user.name = 'foo bar' |
|
62 |
user.email = 'foo@example.net' |
|
63 |
user.store() |
|
64 |
account = PasswordAccount(id='user') |
|
65 |
account.set_password('user') |
|
66 |
account.user_id = user.id |
|
67 |
account.store() |
|
68 |
""", |
|
69 |
'create-data': u""" |
|
70 |
import datetime |
|
71 |
import random |
|
72 |
from quixote import get_publisher |
|
73 | ||
74 |
from wcs.categories import Category |
|
75 |
from wcs.formdef import FormDef |
|
76 |
from wcs.roles import Role |
|
77 |
from wcs import fields |
|
78 | ||
79 |
cat = Category() |
|
80 |
cat.name = 'Catégorie' |
|
81 |
cat.description = '' |
|
82 |
cat.store() |
|
83 | ||
84 |
formdef = FormDef() |
|
85 |
formdef.name = 'Demande' |
|
86 |
formdef.category_id = cat.id |
|
87 |
formdef.fields = [ |
|
88 |
fields.StringField(id='1', label='1st field', type='string', anonymise=False, varname='field_string'), |
|
89 |
fields.ItemField(id='2', label='2nd field', type='item', |
|
90 |
items=['foo', 'bar', 'baz'], varname='field_item'), |
|
91 |
] |
|
92 |
formdef.store() |
|
93 | ||
94 |
user = get_publisher().user_class.select()[0] |
|
95 | ||
96 |
for i in range(50): |
|
97 |
formdata = formdef.data_class()() |
|
98 |
formdata.just_created() |
|
99 |
formdata.receipt_time = datetime.datetime(2018, random.randrange(1, 13), random.randrange(1, 29)).timetuple() |
|
100 |
formdata.data = {'1': 'FOO BAR %d' % i} |
|
101 |
if i%4 == 0: |
|
102 |
formdata.data['2'] = 'foo' |
|
103 |
formdata.data['2_display'] = 'foo' |
|
104 |
elif i%4 == 1: |
|
105 |
formdata.data['2'] = 'bar' |
|
106 |
formdata.data['2_display'] = 'bar' |
|
107 |
else: |
|
108 |
formdata.data['2'] = 'baz' |
|
109 |
formdata.data['2_display'] = 'baz' |
|
110 |
if i%3 == 0: |
|
111 |
formdata.jump_status('new') |
|
112 |
else: |
|
113 |
formdata.jump_status('finished') |
|
114 |
if i%7 == 0: |
|
115 |
formdata.user_id = user.id |
|
116 |
formdata.store() |
|
117 |
""", |
|
118 |
} |
|
119 | ||
120 | ||
121 |
@pytest.fixture(scope='session') |
|
122 |
def wcs(tmp_path_factory): |
|
123 |
'''Session scoped wcs fixture, so read-only.''' |
|
124 |
if 'WCSCTL' not in os.environ or not os.path.exists(os.environ['WCSCTL']): |
|
125 |
pytest.skip('WCSCTL not defined in environment') |
|
126 |
WCSCTL = os.environ.get('WCSCTL') |
|
127 |
WCS_DIR = tmp_path_factory.mktemp('wcs') |
|
128 |
HOSTNAME = '127.0.0.1' |
|
129 |
PORT = 8899 |
|
130 |
ADDRESS = '0.0.0.0' |
|
131 |
WCS_PID = None |
|
132 | ||
133 |
def run_wcs_script(script, hostname): |
|
134 |
'''Run python script inside w.c.s. environment''' |
|
135 | ||
136 |
script_path = WCS_DIR / (script + '.py') |
|
137 |
with script_path.open('w') as fd: |
|
138 |
fd.write(WCS_SCRIPTS[script]) |
|
139 | ||
140 |
subprocess.check_call( |
|
141 |
[WCSCTL, 'runscript', '--app-dir', str(WCS_DIR), '--vhost', hostname, |
|
142 |
str(script_path)]) |
|
143 | ||
144 |
tenant_dir = WCS_DIR / HOSTNAME |
|
145 |
tenant_dir.mkdir() |
|
146 | ||
147 |
run_wcs_script('setup-auth', HOSTNAME) |
|
148 |
run_wcs_script('create-user', HOSTNAME) |
|
149 |
run_wcs_script('create-data', HOSTNAME) |
|
150 | ||
151 |
with (tenant_dir / 'site-options.cfg').open('w') as fd: |
|
152 |
fd.write(u'''[api-secrets] |
|
153 |
olap = olap |
|
154 |
''') |
|
155 | ||
156 |
with (WCS_DIR / 'wcs.cfg').open('w') as fd: |
|
157 |
fd.write(u'''[main] |
|
158 |
app_dir = %s\n''' % WCS_DIR) |
|
159 | ||
160 |
with (WCS_DIR / 'local_settings.py').open('w') as fd: |
|
161 |
fd.write(u''' |
|
162 |
WCS_LEGACY_CONFIG_FILE = '%s/wcs.cfg' |
|
163 |
THEMES_DIRECTORY = '/' |
|
164 |
ALLOWED_HOSTS = ['%s'] |
|
165 |
''' % (WCS_DIR, HOSTNAME)) |
|
166 | ||
167 |
# launch a Django worker for running w.c.s. |
|
168 |
WCS_PID = os.fork() |
|
169 |
if not WCS_PID: |
|
170 |
os.chdir(os.path.dirname(WCSCTL)) |
|
171 |
os.environ['DJANGO_SETTINGS_MODULE'] = 'wcs.settings' |
|
172 |
os.environ['WCS_SETTINGS_FILE'] = str(WCS_DIR / 'local_settings.py') |
|
173 |
os.execvp('python', ['python', 'manage.py', 'runserver', '--noreload', '%s:%s' % (ADDRESS, PORT)]) |
|
174 |
sys.exit(0) |
|
175 | ||
176 |
# verify w.c.s. is launched |
|
177 |
s = socket.socket() |
|
178 |
i = 0 |
|
179 |
while True: |
|
180 |
i += 1 |
|
181 |
try: |
|
182 |
s.connect((ADDRESS, PORT)) |
|
183 |
except Exception: |
|
184 |
time.sleep(0.1) |
|
185 |
else: |
|
186 |
s.close() |
|
187 |
break |
|
188 |
assert i < 50, 'no connection found after 5 seconds' |
|
189 | ||
190 |
# verify w.c.s. is still running |
|
191 |
pid, exit_code = os.waitpid(WCS_PID, os.WNOHANG) |
|
192 |
if pid: |
|
193 |
assert False, 'w.c.s. stopped with exit-code %s' % exit_code |
|
194 | ||
195 |
yield Wcs(url='http://%s:%s/' % (HOSTNAME, PORT), appdir=WCS_DIR, pid=WCS_PID) |
|
196 |
os.kill(WCS_PID, 9) |
|
197 |
shutil.rmtree(str(WCS_DIR)) |
tests/test_wcs.py | ||
---|---|---|
1 |
import subprocess |
|
2 | ||
3 | ||
4 |
def test_wcs_fixture(wcs, postgres_db, tmpdir, caplog): |
|
5 |
config_ini = tmpdir / 'config.ini' |
|
6 |
model_dir = tmpdir / 'model_dir' |
|
7 |
model_dir.mkdir() |
|
8 |
with config_ini.open('w') as fd: |
|
9 |
fd.write(u''' |
|
10 |
[wcs-olap] |
|
11 |
cubes_model_dirs = {model_dir} |
|
12 |
pg_dsn = {dsn} |
|
13 | ||
14 |
[{wcs.url}] |
|
15 |
orig = olap |
|
16 |
key = olap |
|
17 |
schema = public |
|
18 |
'''.format(wcs=wcs, model_dir=model_dir, dsn=postgres_db.dsn)) |
|
19 | ||
20 |
from wcs_olap import cmd |
|
21 |
import sys |
|
22 | ||
23 |
sys.argv = ['', '--no-log-errors', str(config_ini)] |
|
24 |
cmd.main2() |
|
25 | ||
26 |
expected_schema = [ |
|
27 |
('agent', 'id'), |
|
28 |
('agent', 'label'), |
|
29 |
('category', 'id'), |
|
30 |
('category', 'label'), |
|
31 |
('channel', 'id'), |
|
32 |
('channel', 'label'), |
|
33 |
('evolution', 'id'), |
|
34 |
('evolution', 'generic_status_id'), |
|
35 |
('evolution', 'formdata_id'), |
|
36 |
('evolution', 'time'), |
|
37 |
('evolution', 'date'), |
|
38 |
('evolution', 'hour_id'), |
|
39 |
('evolution_demande', 'id'), |
|
40 |
('evolution_demande', 'status_id'), |
|
41 |
('evolution_demande', 'formdata_id'), |
|
42 |
('evolution_demande', 'time'), |
|
43 |
('evolution_demande', 'date'), |
|
44 |
('evolution_demande', 'hour_id'), |
|
45 |
('formdata', 'id'), |
|
46 |
('formdata', 'formdef_id'), |
|
47 |
('formdata', 'receipt_time'), |
|
48 |
('formdata', 'hour_id'), |
|
49 |
('formdata', 'channel_id'), |
|
50 |
('formdata', 'backoffice'), |
|
51 |
('formdata', 'generic_status_id'), |
|
52 |
('formdata', 'endpoint_delay'), |
|
53 |
('formdata', 'first_agent_id'), |
|
54 |
('formdata', 'geolocation_base'), |
|
55 |
('formdata', 'json_data'), |
|
56 |
('formdata_demande', 'id'), |
|
57 |
('formdata_demande', 'formdef_id'), |
|
58 |
('formdata_demande', 'receipt_time'), |
|
59 |
('formdata_demande', 'hour_id'), |
|
60 |
('formdata_demande', 'channel_id'), |
|
61 |
('formdata_demande', 'backoffice'), |
|
62 |
('formdata_demande', 'generic_status_id'), |
|
63 |
('formdata_demande', 'endpoint_delay'), |
|
64 |
('formdata_demande', 'first_agent_id'), |
|
65 |
('formdata_demande', 'geolocation_base'), |
|
66 |
('formdata_demande', 'json_data'), |
|
67 |
('formdata_demande', 'status_id'), |
|
68 |
('formdata_demande', 'field_field_item'), |
|
69 |
('formdata_demande', 'function__receiver'), |
|
70 |
('formdata_demande_field_field_item', 'id'), |
|
71 |
('formdata_demande_field_field_item', 'label'), |
|
72 |
('formdef', 'id'), |
|
73 |
('formdef', 'category_id'), |
|
74 |
('formdef', 'label'), |
|
75 |
('hour', 'id'), |
|
76 |
('hour', 'label'), |
|
77 |
('role', 'id'), |
|
78 |
('role', 'label'), |
|
79 |
('status', 'id'), |
|
80 |
('status', 'label'), |
|
81 |
('status_demande', 'id'), |
|
82 |
('status_demande', 'label') |
|
83 |
] |
|
84 | ||
85 |
with postgres_db.conn() as conn: |
|
86 |
with conn.cursor() as c: |
|
87 |
c.execute('SELECT table_name, column_name ' |
|
88 |
'FROM information_schema.columns ' |
|
89 |
'WHERE table_schema = \'public\' ORDER BY table_name, ordinal_position') |
|
90 | ||
91 |
assert list(c.fetchall()) == expected_schema |
tox.ini | ||
---|---|---|
9 | 9 |
[testenv] |
10 | 10 |
usedevelop = true |
11 | 11 |
setenv = |
12 |
WCSCTL=wcs/wcsctl.py |
|
12 | 13 |
coverage: COVERAGE=--junit-xml=junit.xml --cov=src --cov-report xml |
13 | 14 |
deps = |
14 | 15 |
coverage |
15 | 16 |
pytest |
16 | 17 |
pytest-cov |
17 | 18 |
pytest-random |
19 |
quixote<3.0 |
|
20 |
psycopg2-binary |
|
21 |
vobject |
|
22 |
gadjo |
|
23 |
django>=1.11,<1.12 |
|
18 | 24 |
commands = |
19 |
py.test {env:COVERAGE:} {posargs:--random tests} |
|
25 |
./get_wcs.sh |
|
26 |
py.test {env:COVERAGE:} {posargs:--random-group tests} |
wcs_olap/cmd.py | ||
---|---|---|
49 | 49 |
group = parser.add_mutually_exclusive_group() |
50 | 50 |
parser.add_argument('--no-feed', dest='feed', help='only produce the model', |
51 | 51 |
action='store_false', default=True) |
52 |
parser.add_argument('--no-log-errors', dest='no_log_errors', |
|
53 |
action='store_true', default=False) |
|
52 | 54 |
parser.add_argument('--fake', action='store_true', default=False) |
53 | 55 |
group.add_argument("-a", "--all", help="synchronize all wcs", action='store_true', |
54 | 56 |
default=False) |
... | ... | |
111 | 113 |
logger.info('finished') |
112 | 114 |
feed_result = False |
113 | 115 |
except: |
116 |
if args.no_log_errors: |
|
117 |
raise |
|
114 | 118 |
feed_result = True |
115 | 119 |
logger.exception('failed to synchronize with %s', url) |
116 | 120 |
failure = failure or feed_result |
117 |
- |