• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CenterForOpenScience / SHARE / 4872982462

03 May 2023 02:24PM UTC coverage: 91.829% (+7.5%) from 84.377%
4872982462

Pull #799

github

GitHub
Merge c24335241 into ff3a9add6
Pull Request #799: Release/23.0.0

2550 of 2550 new or added lines in 59 files covered. (100.0%)

23341 of 25418 relevant lines covered (91.83%)

0.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.07
/tests/share/util/test_mutable_graph.py
1
import pytest
1✔
2

3
from share.util.graph import MutableGraph, MutableGraphError
1✔
4

5

6
work_id = '_:6203fec461bb4b3fa956772acbd9c50d'
1✔
7
org_id = '_:d486fd737bea4fbe9566b7a2842651ef'
1✔
8
uni_id = '_:d486fd737beeffbe9566b7a2842651ef'
1✔
9
person_id = '_:f4cec0271c7d4085bac26dbb2b32a002'
1✔
10
creator_id = '_:a17f28109536459ca02d99bf777400ae'
1✔
11
identifier_id = '_:a27f2810e536459ca02d99bf707400be'
1✔
12

13

14
@pytest.fixture
1✔
15
def example_graph_nodes():
1✔
16
    return [
1✔
17
        {'@id': uni_id, '@type': 'Institution', 'name': 'University of Whales'},
1✔
18
        {'@id': org_id, '@type': 'Organization', 'name': 'Department of Physics'},
1✔
19

20
        {'@id': '_:org-uni', '@type': 'IsMemberOf', 'subject': {'@id': org_id, '@type': 'Organization'}, 'related': {'@id': uni_id, '@type': 'Institution'}},
1✔
21

22
        {'@id': '_:tag1', '@type': 'Tag', 'name': 'you\'re'},
1✔
23
        {'@id': '_:tag2', '@type': 'Tag', 'name': 'it'},
1✔
24
        {'@id': '_:ttag1', '@type': 'ThroughTags', 'tag': {'@id': '_:tag1', '@type': 'Tag'}, 'creative_work': {'@id': work_id, '@type': 'Article'}},
1✔
25
        {'@id': '_:ttag2', '@type': 'ThroughTags', 'tag': {'@id': '_:tag2', '@type': 'Tag'}, 'creative_work': {'@id': work_id, '@type': 'Article'}},
1✔
26

27
        {'@id': '_:c4f10e02785a4b4d878f48d08ffc7fce', 'related': {'@type': 'Organization', '@id': org_id}, '@type': 'IsAffiliatedWith', 'subject': {'@type': 'Person', '@id': '_:7e742fa3377e4f119e36f8629144a0bc'}},
1✔
28
        {'@id': '_:7e742fa3377e4f119e36f8629144a0bc', 'agent_relations': [{'@type': 'IsAffiliatedWith', '@id': '_:c4f10e02785a4b4d878f48d08ffc7fce'}], '@type': 'Person', 'family_name': 'Prendergast', 'given_name': 'David'},
1✔
29
        {'@id': '_:687a4ba2cbd54ab7a2f2c3cd1777ea8a', '@type': 'Creator', 'creative_work': {'@type': 'Article', '@id': work_id}, 'agent': {'@type': 'Person', '@id': '_:7e742fa3377e4f119e36f8629144a0bc'}},
1✔
30

31
        {'@id': '_:69e859cefed140bd9b717c5b610d300c', '@type': 'Organization', 'name': 'NMRC, University College, Cork, Ireland'},
1✔
32

33
        {'@id': '_:2fd829eeda214adca2d4d34d02b10328', 'related': {'@type': 'Organization', '@id': '_:69e859cefed140bd9b717c5b610d300c'}, '@type': 'IsAffiliatedWith', 'subject': {'@type': 'Person', '@id': '_:ed3cc2a50f6d499db933a28d16bca5d6'}},
1✔
34
        {'@id': '_:ed3cc2a50f6d499db933a28d16bca5d6', 'agent_relations': [{'@type': 'IsAffiliatedWith', '@id': '_:2fd829eeda214adca2d4d34d02b10328'}], '@type': 'Person', 'family_name': 'Nolan', 'given_name': 'M.'},
1✔
35
        {'@id': '_:27961f3c7c644101a500772477aff304', '@type': 'Creator', 'creative_work': {'@type': 'Article', '@id': work_id}, 'agent': {'@type': 'Person', '@id': '_:ed3cc2a50f6d499db933a28d16bca5d6'}},
1✔
36

37
        {'@id': '_:d4f10e02785a4b4d878f48d08ffc7fce', 'related': {'@type': 'Organization', '@id': org_id}, '@type': 'IsAffiliatedWith', 'subject': {'@type': 'Person', '@id': '_:9a1386475d314b9bb524931e24361aaa'}},
1✔
38
        {'@id': '_:9a1386475d314b9bb524931e24361aaa', 'agent_relations': [{'@type': 'IsAffiliatedWith', '@id': '_:d4f10e02785a4b4d878f48d08ffc7fce'}], '@type': 'Person', 'family_name': 'Filippi', 'given_name': 'Claudia'},
1✔
39
        {'@id': '_:bf7726af4542405888463c796e5b7686', '@type': 'Creator', 'creative_work': {'@type': 'Article', '@id': work_id}, 'agent': {'@type': 'Person', '@id': '_:9a1386475d314b9bb524931e24361aaa'}},
1✔
40

41
        {'@id': '_:e4f10e02785a4b4d878f48d08ffc7fce', 'related': {'@type': 'Organization', '@id': org_id}, '@type': 'IsAffiliatedWith', 'subject': {'@type': 'Person', '@id': '_:78639db07e2e4ee88b422a8920d8a095'}},
1✔
42
        {'@id': '_:78639db07e2e4ee88b422a8920d8a095', 'agent_relations': [{'@type': 'IsAffiliatedWith', '@id': '_:e4f10e02785a4b4d878f48d08ffc7fce'}], '@type': 'Person', 'family_name': 'Fahy', 'given_name': 'Stephen'},
1✔
43
        {'@id': '_:18d151204d7c431388a7e516defab1bc', '@type': 'Creator', 'creative_work': {'@type': 'Article', '@id': work_id}, 'agent': {'@type': 'Person', '@id': '_:78639db07e2e4ee88b422a8920d8a095'}},
1✔
44

45
        {'@id': '_:5fd829eeda214adca2d4d34d02b10328', 'related': {'@type': 'Organization', '@id': '_:69e859cefed140bd9b717c5b610d300c'}, '@type': 'IsAffiliatedWith', 'subject': {'@type': 'Person', '@id': person_id}},
1✔
46
        {'@id': person_id, 'agent_relations': [{'@type': 'IsAffiliatedWith', '@id': '_:5fd829eeda214adca2d4d34d02b10328'}], '@type': 'Person', 'family_name': 'Greer', 'given_name': 'J.'},
1✔
47
        {'@id': creator_id, '@type': 'Creator', 'creative_work': {'@type': 'Article', '@id': work_id}, 'agent': {'@type': 'Person', '@id': person_id}},
1✔
48
        {'@id': identifier_id, '@type': 'WorkIdentifier', 'creative_work': {'@type': 'Article', '@id': work_id}, 'uri': 'http://example.com/things'},
1✔
49
        {'@id': work_id, 'date_updated': '2016-10-20T00:00:00+00:00', 'identifiers': [{'@type': 'WorkIdentifier', '@id': identifier_id}], 'agent_relations': [{'@type': 'Creator', '@id': '_:687a4ba2cbd54ab7a2f2c3cd1777ea8a'}, {'@type': 'Creator', '@id': '_:27961f3c7c644101a500772477aff304'}, {'@type': 'Creator', '@id': '_:bf7726af4542405888463c796e5b7686'}, {'@type': 'Creator', '@id': '_:18d151204d7c431388a7e516defab1bc'}, {'@type': 'Creator', '@id': creator_id}], 'title': 'Impact of Electron-Electron Cusp on Configuration Interaction Energies', '@type': 'Article', 'description': '  The effect of the electron-electron cusp on the convergence of configuration\ninteraction (CI) wave functions is examined. By analogy with the\npseudopotential approach for electron-ion interactions, an effective\nelectron-electron interaction is developed which closely reproduces the\nscattering of the Coulomb interaction but is smooth and finite at zero\nelectron-electron separation. The exact many-electron wave function for this\nsmooth effective interaction has no cusp at zero electron-electron separation.\nWe perform CI and quantum Monte Carlo calculations for He and Be atoms, both\nwith the Coulomb electron-electron interaction and with the smooth effective\nelectron-electron interaction. We find that convergence of the CI expansion of\nthe wave function for the smooth electron-electron interaction is not\nsignificantly improved compared with that for the divergent Coulomb interaction\nfor energy differences on the order of 1 mHartree. This shows that, contrary to\npopular belief, description of the electron-electron cusp is not a limiting\nfactor, to within chemical accuracy, for CI calculations.\n'}  # noqa
1✔
50
    ]
51

52

53
@pytest.fixture
1✔
54
def example_graph(example_graph_nodes):
1✔
55
    return MutableGraph.from_jsonld(example_graph_nodes)
1✔
56

57

58
class TestMutableGraph:
1✔
59
    def test_graph(self, example_graph):
1✔
60
        assert example_graph.number_of_nodes() == 25
1✔
61

62
    @pytest.mark.parametrize('node_id', [work_id, org_id, person_id, creator_id])
1✔
63
    def test_get_node(self, example_graph, node_id):
1✔
64
        assert example_graph.get_node(node_id).id == node_id
1✔
65

66
    def test_get_nonexistent_node(self, example_graph):
1✔
67
        assert example_graph.get_node('not_an_id') is None
1✔
68

69
    def test_edge(self, example_graph):
1✔
70
        creator_node = example_graph.get_node(creator_id)
1✔
71
        assert creator_node['creative_work'] == example_graph.get_node(work_id)
1✔
72
        assert creator_node['agent'] == example_graph.get_node(person_id)
1✔
73

74
    @pytest.mark.parametrize('node_id, key, value', [
1✔
75
        (work_id, 'title', 'title title'),
1✔
76
        (work_id, 'description', 'woo'),
1✔
77
        (identifier_id, 'creative_work', None),
1✔
78
        (identifier_id, 'foo', 'bar'),
1✔
79
    ])
80
    def test_set_attrs(self, example_graph, node_id, key, value):
1✔
81
        n = example_graph.get_node(node_id)
1✔
82
        n[key] = value
1✔
83
        assert n[key] == value
1✔
84

85
    @pytest.mark.parametrize('set_none', [True, False])
1✔
86
    def test_del_attrs(self, example_graph, set_none):
1✔
87
        work = example_graph.get_node(work_id)
1✔
88
        assert work['title']
1✔
89
        if set_none:
1✔
90
            work['title'] = None
1✔
91
        else:
×
92
            del work['title']
1✔
93
        assert work['title'] is None
1✔
94
        assert 'title' not in work.attrs()
1✔
95

96
        identifier = example_graph.get_node(identifier_id)
1✔
97
        assert identifier['creative_work'] == work
1✔
98
        if set_none:
1✔
99
            identifier['creative_work'] = None
1✔
100
        else:
×
101
            del identifier['creative_work']
1✔
102

103
    @pytest.mark.parametrize('node_id, reverse_edge_name, count', [
1✔
104
        (work_id, 'agent_relations', 5),
1✔
105
        (work_id, 'incoming_creative_work_relations', 0),
1✔
106
        (work_id, 'identifiers', 1),
1✔
107
        (org_id, 'incoming_agent_relations', 3),
1✔
108
        (org_id, 'outgoing_agent_relations', 1),
1✔
109
    ])
110
    def test_reverse_edge(self, example_graph, node_id, reverse_edge_name, count):
1✔
111
        node = example_graph.get_node(node_id)
1✔
112
        assert len(node[reverse_edge_name]) == count
1✔
113

114
    @pytest.mark.parametrize('node_id, m2m_name, count', [
1✔
115
        (work_id, 'related_agents', 5),
1✔
116
        (work_id, 'related_works', 0),
1✔
117
        (work_id, 'subjects', 0),
1✔
118
        (work_id, 'tags', 2),
1✔
119
        (org_id, 'related_works', 0),
1✔
120
        (org_id, 'related_agents', 4),
1✔
121
    ])
122
    def test_many_to_many(self, example_graph, node_id, m2m_name, count):
1✔
123
        node = example_graph.get_node(node_id)
1✔
124
        assert len(node[m2m_name]) == count
1✔
125

126
    @pytest.mark.parametrize('node_id, count', [
1✔
127
        (work_id, 16),
1✔
128
        (org_id, 20),
1✔
129
        (person_id, 22),
1✔
130
        (creator_id, 24),
1✔
131
    ])
132
    def test_remove_node_cascades(self, example_graph, node_id, count):
1✔
133
        example_graph.remove_node(node_id)
1✔
134
        assert example_graph.number_of_nodes() == count
1✔
135

136
    def test_add_node(self, example_graph):
1✔
137
        identifier_id = '_:foo'
1✔
138
        uri = 'mailto:person@example.com'
1✔
139
        person = example_graph.get_node(person_id)
1✔
140
        node_count = example_graph.number_of_nodes()
1✔
141
        assert len(person['identifiers']) == 0
1✔
142

143
        example_graph.add_node(identifier_id, 'AgentIdentifier', {'uri': uri, 'agent': person})
1✔
144

145
        assert example_graph.number_of_nodes() == node_count + 1
1✔
146
        identifier_node = example_graph.get_node(identifier_id)
1✔
147
        assert identifier_node['uri'] == uri
1✔
148
        assert identifier_node['agent'] == person
1✔
149

150
        identifiers = person['identifiers']
1✔
151
        assert len(identifiers) == 1
1✔
152
        assert identifier_node == next(iter(identifiers))
1✔
153

154
    @pytest.mark.parametrize('count, filter', [
1✔
155
        (5, lambda n, g: n.type == 'person'),
1✔
156
        (0, lambda n, g: not g.degree(n.id)),
1✔
157
        (1, lambda n, g: len(g.out_edges(n.id)) == 1),
1✔
158
    ])
159
    def test_filter_nodes(self, example_graph, filter, count):
1✔
160
        filtered = list(example_graph.filter_nodes(lambda n: filter(n, example_graph)))
1✔
161
        assert len(filtered) == count
1✔
162

163
    def test_jsonld(self, example_graph_nodes, example_graph):
1✔
164
        def clean_jsonld(value):
1✔
165
            if isinstance(value, list):
1✔
166
                return [clean_jsonld(n) for n in sorted(value, key=lambda n: n['@id'])]
1✔
167
            if isinstance(value, dict):
1✔
168
                return {
1✔
169
                    k: v.lower() if k == '@type' else clean_jsonld(v)
1✔
170
                    for k, v in value.items() if not isinstance(v, list)
1✔
171
                }
172
            return value
1✔
173
        expected_nodes = clean_jsonld(example_graph_nodes)
1✔
174
        actual = example_graph.to_jsonld(in_edges=False)
1✔
175
        actual_nodes = clean_jsonld(actual['@graph'])
1✔
176
        assert expected_nodes == actual_nodes
1✔
177
        assert actual['central_node_id'] is None
1✔
178

179

180
class TestCentralWork:
1✔
181
    def test_obvious_example(self, example_graph):
1✔
182
        assert example_graph.central_node_id is None
1✔
183
        assert example_graph.get_central_node() is None
1✔
184

185
        guessed = example_graph.get_central_node(guess=True)
1✔
186
        assert guessed.id == work_id
1✔
187
        # side-effect: now the graph knows its central node
188
        assert example_graph.central_node_id == work_id
1✔
189
        assert example_graph.get_central_node() == guessed
1✔
190

191
    def test_no_central_node(self):
1✔
192
        graph = MutableGraph()
1✔
193
        agent = graph.add_node(None, 'agent')
1✔
194
        graph.add_node(None, 'agentidentifier', {'agent': agent})
1✔
195

196
        assert graph.get_central_node() is None
1✔
197
        assert graph.get_central_node(guess=True) is None
1✔
198
        assert graph.central_node_id is None
1✔
199

200
    def test_explicit_central_node(self):
1✔
201
        central_id = '_:center'
1✔
202

203
        graph = MutableGraph.from_jsonld({
1✔
204
            'central_node_id': central_id,
1✔
205
            '@graph': [
1✔
206
                {'@id': central_id, '@type': 'creativework'},
1✔
207
                {'@id': '_:other', '@type': 'creativework', 'title': 'looks like the center'},
1✔
208
            ],
209
        })
210
        assert graph.central_node_id == central_id
1✔
211
        assert graph.get_central_node().id == central_id
1✔
212
        assert graph.get_central_node(guess=True).id == central_id
1✔
213

214
    def test_ambiguous_central_node(self):
1✔
215
        graph = MutableGraph.from_jsonld({
1✔
216
            '@graph': [
1✔
217
                {'@id': '_:thing1', '@type': 'creativework', 'title': 'looks like the center'},
1✔
218
                {'@id': '_:thing2', '@type': 'creativework', 'title': 'also looks like the center'},
1✔
219
            ],
220
        })
221
        with pytest.raises(MutableGraphError):
1✔
222
            graph.get_central_node(guess=True)
1✔
223

224
    @pytest.mark.parametrize('graph_nodes, expected_guess', [
1✔
225
        (
1✔
226
            [
1✔
227
                {'@id': '_:thing1', '@type': 'creativework', 'title': 'only thing'},
1✔
228
            ],
229
            '_:thing1',
1✔
230
        ),
231
        (
1✔
232
            [
1✔
233
                {'@id': '_:thing1', '@type': 'creativework', 'title': 'one thing'},
1✔
234
                {'@id': '_:thing2', '@type': 'creativework', 'title': 'another thing', 'description': 'tiebreaker'},
1✔
235
            ],
236
            '_:thing2',
1✔
237
        ),
238
        (
1✔
239
            [
1✔
240
                {'@id': '_:thing1', '@type': 'creativework', 'title': 'looks like the center'},
1✔
241
                {'@id': '_:thing2', '@type': 'creativework', 'title': 'also looks like the center'},
1✔
242
                {
1✔
243
                    '@id': '_:tiebreaker',
1✔
244
                    '@type': 'workidentifier',
1✔
245
                    'uri': 'http://example.com/woo',
1✔
246
                    'creative_work': {'@id': '_:thing2', '@type': 'creativework'},
1✔
247
                },
248
            ],
249
            '_:thing2',
1✔
250
        ),
251
        (
1✔
252
            [
1✔
253
                {'@id': '_:thing1', '@type': 'creativework', 'title': 'looks like the center', 'description': 'confounder'},
1✔
254
                {'@id': '_:thing2', '@type': 'creativework', 'title': 'also looks like the center'},
1✔
255
                {
1✔
256
                    '@id': '_:tiebreaker',
1✔
257
                    '@type': 'workidentifier',
1✔
258
                    'uri': 'http://example.com/woo',
1✔
259
                    'creative_work': {'@id': '_:thing2', '@type': 'creativework'},
1✔
260
                },
261
            ],
262
            '_:thing2',
1✔
263
        ),
264
        (
1✔
265
            [
1✔
266
                {'@id': '_:thing1', '@type': 'creativework', 'title': 'parent'},
1✔
267
                {'@id': '_:thing2', '@type': 'creativework', 'title': 'child'},
1✔
268
                {
1✔
269
                    '@id': '_:choose-the-child',
1✔
270
                    '@type': 'ispartof',
1✔
271
                    'subject': {'@id': '_:thing2', '@type': 'creativework'},
1✔
272
                    'related': {'@id': '_:thing1', '@type': 'creativework'},
1✔
273
                },
274
            ],
275
            '_:thing2',
1✔
276
        ),
277
    ])
278
    def test_guessing(self, graph_nodes, expected_guess):
1✔
279
        graph = MutableGraph.from_jsonld({'@graph': graph_nodes})
1✔
280
        guessed = graph.get_central_node(guess=True)
1✔
281
        assert guessed.id == expected_guess
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc