Skip to content

Commit a3f376e

Browse files
committed
PLAT-245 -- Add testing examples.
1 parent 1d199ee commit a3f376e

File tree

5 files changed

+373
-0
lines changed

5 files changed

+373
-0
lines changed

docs/__init__.py

Whitespace-only changes.

docs/testing/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# -*- coding: utf-8 -*-

docs/testing/models_left.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# -*- coding: utf-8 -*-
2+
from sqlalchemy import Column, ForeignKey, Integer, String, Unicode
3+
from sqlalchemy.ext.declarative import declarative_base
4+
5+
6+
Base = declarative_base()
7+
8+
9+
class Employee(Base):
10+
__tablename__ = "employees"
11+
12+
id = Column(Integer, primary_key=True)
13+
name = Column(Unicode(200), unique=True, index=True)
14+
age = Column(Integer, nullable=False, default=21)
15+
ssn = Column(Unicode(30), nullable=False)
16+
number_of_pets = Column(Integer, default=1, nullable=False)
17+
18+
company_id = Column(
19+
Integer,
20+
ForeignKey("companies.id", name="fk_employees_companies"),
21+
nullable=False
22+
)
23+
24+
role_id = Column(
25+
Integer,
26+
ForeignKey("roles.id", name="fk_employees_roles"),
27+
nullable=False
28+
)
29+
30+
31+
class Company(Base):
32+
__tablename__ = "companies"
33+
34+
id = Column(Integer, primary_key=True)
35+
name = Column(Unicode(200), nullable=False, unique=True)
36+
37+
38+
class Role(Base):
39+
__tablename__ = "roles"
40+
41+
id = Column(Integer, primary_key=True)
42+
name = Column(Unicode(50), nullable=False)
43+
44+
45+
class Skill(Base):
46+
__tablename__ = "skills"
47+
48+
slug = Column(String(50), primary_key=True)
49+
description = Column(Unicode(100), nullable=True)
50+
51+
employee = Column(
52+
Integer,
53+
ForeignKey("employees.id", name="fk_skills_employees"),
54+
nullable=False
55+
)
56+
57+
58+
class MobileNumber(Base):
59+
__tablename__ = "mobile_numbers"
60+
61+
id = Column(Integer, primary_key=True)
62+
number = Column(String(40), nullable=False)
63+
64+
owner = Column(
65+
Integer,
66+
ForeignKey("employees.id", name="fk_mobile_numbers_employees"),
67+
nullable=False
68+
)

docs/testing/models_right.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# -*- coding: utf-8 -*-
2+
from sqlalchemy import Column, ForeignKey, Integer, String, Unicode
3+
from sqlalchemy.ext.declarative import declarative_base
4+
5+
6+
Base = declarative_base()
7+
8+
9+
class Employee(Base):
10+
__tablename__ = "employees"
11+
12+
id = Column(Integer, primary_key=True)
13+
name = Column(Unicode(200), unique=True, index=False)
14+
age = Column(Integer, nullable=False, default=21)
15+
ssn = Column(Unicode(30), nullable=False)
16+
number_of_pets = Column(Integer, default=1, nullable=False)
17+
18+
company_id = Column(
19+
Integer,
20+
ForeignKey("companies.id", name="fk_emp_comp"),
21+
nullable=False
22+
)
23+
24+
role_id = Column(
25+
Integer,
26+
ForeignKey("roles.id", name="fk_employees_roles"),
27+
nullable=False
28+
)
29+
30+
31+
class Company(Base):
32+
__tablename__ = "companies"
33+
34+
id = Column(Integer, primary_key=True)
35+
name = Column(Unicode(200), nullable=True, unique=False)
36+
37+
38+
class Role(Base):
39+
__tablename__ = "roles"
40+
41+
id = Column(Integer, primary_key=True)
42+
name = Column(Unicode(60), nullable=False)
43+
44+
45+
class Skill(Base):
46+
__tablename__ = "skills"
47+
48+
id = Column(Integer, primary_key=True)
49+
slug = Column(String(50))
50+
description = Column(Unicode(100), nullable=True)
51+
52+
employee = Column(
53+
Integer,
54+
ForeignKey("employees.id", name="fk_skills_employees"),
55+
nullable=False
56+
)
57+
58+
59+
class PhoneNumber(Base):
60+
__tablename__ = "phone_numbers"
61+
62+
id = Column(Integer, primary_key=True)
63+
number = Column(String(40), nullable=False)
64+
65+
owner = Column(
66+
Integer,
67+
ForeignKey("employees.id", name="fk_phone_numbers_employees"),
68+
nullable=False
69+
)

docs/testing/test_example.py

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
# -*- coding: utf-8 -*-
2+
import json
3+
4+
import pytest
5+
6+
from sqlalchemydiff.comparer import compare
7+
from sqlalchemydiff.util import prepare_schema_from_models, walk_dict
8+
from .models_left import Base as Base_left
9+
from .models_right import Base as Base_right
10+
11+
from test import assert_items_equal
12+
13+
14+
@pytest.mark.usefixtures("new_db_left")
15+
@pytest.mark.usefixtures("new_db_right")
16+
def test_same_schema_is_the_same(uri_left, uri_right):
17+
prepare_schema_from_models(uri_left, Base_right)
18+
prepare_schema_from_models(uri_right, Base_right)
19+
20+
result = compare(uri_left, uri_right)
21+
22+
assert result.is_match
23+
24+
25+
@pytest.mark.usefixtures("new_db_left")
26+
@pytest.mark.usefixtures("new_db_right")
27+
def test_schemas_are_different(uri_left, uri_right):
28+
prepare_schema_from_models(uri_left, Base_left)
29+
prepare_schema_from_models(uri_right, Base_right)
30+
31+
result = compare(uri_left, uri_right)
32+
33+
assert not result.is_match
34+
35+
36+
@pytest.mark.usefixtures("new_db_left")
37+
@pytest.mark.usefixtures("new_db_right")
38+
def test_errors_dict_catches_all_differences(uri_left, uri_right):
39+
prepare_schema_from_models(uri_left, Base_left)
40+
prepare_schema_from_models(uri_right, Base_right)
41+
42+
result = compare(uri_left, uri_right)
43+
44+
expected_errors = {
45+
'tables': {
46+
'left_only': ['mobile_numbers'],
47+
'right_only': ['phone_numbers'],
48+
},
49+
'tables_data': {
50+
'companies': {
51+
'columns': {
52+
'diff': [
53+
{
54+
'key': 'name',
55+
'left': {
56+
'default': None,
57+
'name': 'name',
58+
'nullable': False,
59+
'type': 'VARCHAR(200)',
60+
},
61+
'right': {
62+
'default': None,
63+
'name': 'name',
64+
'nullable': True,
65+
'type': 'VARCHAR(200)',
66+
}
67+
}
68+
]
69+
},
70+
'indexes': {
71+
'left_only': [
72+
{
73+
'column_names': ['name'],
74+
'name': 'name',
75+
'type': 'UNIQUE',
76+
'unique': True,
77+
}
78+
]
79+
}
80+
},
81+
'employees': {
82+
'foreign_keys': {
83+
'left_only': [
84+
{
85+
'constrained_columns': ['company_id'],
86+
'name': 'fk_employees_companies',
87+
'options': {},
88+
'referred_columns': ['id'],
89+
'referred_schema': None,
90+
'referred_table': 'companies'
91+
}
92+
],
93+
'right_only': [
94+
{
95+
'constrained_columns': ['company_id'],
96+
'name': 'fk_emp_comp',
97+
'options': {},
98+
'referred_columns': ['id'],
99+
'referred_schema': None,
100+
'referred_table': 'companies',
101+
}
102+
]
103+
},
104+
'indexes': {
105+
'left_only': [
106+
{
107+
'column_names': ['name'],
108+
'name': 'ix_employees_name',
109+
'type': 'UNIQUE',
110+
'unique': True,
111+
},
112+
{
113+
'column_names': ['company_id'],
114+
'name': 'fk_employees_companies',
115+
'unique': False,
116+
}
117+
],
118+
'right_only': [
119+
{
120+
'column_names': ['company_id'],
121+
'name': 'fk_emp_comp',
122+
'unique': False,
123+
},
124+
{
125+
'column_names': ['name'],
126+
'name': 'name',
127+
'type': 'UNIQUE',
128+
'unique': True,
129+
}
130+
]
131+
}
132+
},
133+
'roles': {
134+
'columns': {
135+
'diff': [
136+
{
137+
'key': 'name',
138+
'left': {
139+
'default': None,
140+
'name': 'name',
141+
'nullable': False,
142+
'type': 'VARCHAR(50)',
143+
},
144+
'right': {
145+
'default': None,
146+
'name': 'name',
147+
'nullable': False,
148+
'type': 'VARCHAR(60)',
149+
}
150+
}
151+
]
152+
}
153+
},
154+
'skills': {
155+
'columns': {
156+
'diff': [
157+
{
158+
'key': 'slug',
159+
'left': {
160+
'default': None,
161+
'name': 'slug',
162+
'nullable': False,
163+
'type': 'VARCHAR(50)',
164+
},
165+
'right': {
166+
'default': None,
167+
'name': 'slug',
168+
'nullable': True,
169+
'type': 'VARCHAR(50)',
170+
}
171+
}
172+
],
173+
'right_only': [
174+
{
175+
'autoincrement': True,
176+
'default': None,
177+
'name': 'id',
178+
'nullable': False,
179+
'type': 'INTEGER(11)',
180+
}
181+
]
182+
},
183+
'primary_keys': {
184+
'left_only': ['slug'],
185+
'right_only': ['id'],
186+
}
187+
}
188+
},
189+
'uris': {
190+
'left': uri_left,
191+
'right': uri_right
192+
}
193+
}
194+
195+
assert not result.is_match
196+
197+
compare_error_dicts(expected_errors, result.errors)
198+
199+
200+
def compare_error_dicts(err1, err2):
201+
"""Smart comparer of error dicts.
202+
203+
We cannot directly compare a nested dict structure that has lists
204+
as values on some level. The order of the same list in the two dicts
205+
could be different, which would lead to a failure in the comparison,
206+
but it would be wrong as for us the order doesn't matter and we need
207+
a comparison that only checks that the same items are in the lists.
208+
In order to do this, we use the walk_dict function to perform a
209+
smart comparison only on the lists.
210+
211+
This function compares the ``tables`` and ``uris`` items, then it does
212+
an order-insensitive comparison of all lists, and finally it compares
213+
that the sorted JSON dump of both dicts is the same.
214+
"""
215+
assert err1['tables'] == err2['tables']
216+
assert err1['uris'] == err2['uris']
217+
218+
paths = [
219+
['tables_data', 'companies', 'columns', 'diff'],
220+
['tables_data', 'companies', 'indexes', 'left_only'],
221+
['tables_data', 'employees', 'foreign_keys', 'left_only'],
222+
['tables_data', 'employees', 'foreign_keys', 'right_only'],
223+
['tables_data', 'employees', 'indexes', 'left_only'],
224+
['tables_data', 'employees', 'indexes', 'right_only'],
225+
['tables_data', 'roles', 'columns', 'diff'],
226+
['tables_data', 'skills', 'columns', 'diff'],
227+
['tables_data', 'skills', 'columns', 'right_only'],
228+
['tables_data', 'skills', 'primary_keys', 'left_only'],
229+
['tables_data', 'skills', 'primary_keys', 'right_only'],
230+
]
231+
232+
for path in paths:
233+
assert_items_equal(walk_dict(err1, path), walk_dict(err2, path))
234+
235+
assert sorted(json.dumps(err1)) == sorted(json.dumps(err2))

0 commit comments

Comments
 (0)