mrrc 0.7.6

A Rust library for reading, writing, and manipulating MARC bibliographic records in ISO 2709 binary format
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
"""
Unit tests for basic mrrc Python wrapper functionality.
Tests API compatibility with pymarc.
"""

import pytest
from mrrc import MARCReader, Record, Field, Leader, Subfield
import io


def create_field(tag, ind1='0', ind2='0', **subfields):
    """Helper to create a field with subfields."""
    field = Field(tag, ind1, ind2)
    for code, value in subfields.items():
        field.add_subfield(code, value)
    return field


class TestLeaderBasics:
    """Test Leader functionality."""
    
    def test_create_default_leader(self):
        """Test creating a default Leader."""
        leader = Leader()
        assert leader.record_type == 'a'
        assert leader.bibliographic_level == 'm'
        assert leader.record_status == 'n'
    
    def test_leader_properties(self):
        """Test setting/getting leader properties."""
        leader = Leader()
        leader.record_type = 'c'
        assert leader.record_type == 'c'
        
        leader.bibliographic_level = 'd'
        assert leader.bibliographic_level == 'd'


class TestRecordBasics:
    """Test Record creation and basic operations."""
    
    def test_create_empty_record(self):
        """Test creating an empty Record."""
        leader = Leader()
        record = Record(leader)
        assert len(record.fields()) == 0
    
    def test_add_control_field(self):
        """Test adding a control field."""
        leader = Leader()
        record = Record(leader)
        record.add_control_field('001', '12345')
        
        value = record.control_field('001')
        assert value == '12345'
    
    def test_control_field_not_found(self):
        """Test getting non-existent control field."""
        leader = Leader()
        record = Record(leader)
        value = record.control_field('999')
        assert value is None
    
    def test_get_all_control_fields(self):
        """Test getting all control fields."""
        leader = Leader()
        record = Record(leader)
        record.add_control_field('001', '12345')
        record.add_control_field('003', 'ABC')
        
        cfs = record.control_fields()
        assert len(cfs) >= 2


class TestFieldCreation:
    """Test Field and Subfield creation."""
    
    def test_create_field_with_subfields(self):
        """Test creating a field with subfields."""
        field = Field('245', '1', '0')
        field.add_subfield('a', 'The pragmatic programmer :')
        field.add_subfield('b', 'from journeyman to master /')
        assert field.tag == '245'
        assert len(field.subfields()) == 2
    
    def test_subfield_creation(self):
        """Test creating individual Subfield."""
        sf = Subfield('a', 'test value')
        assert sf.code == 'a'
        assert sf.value == 'test value'


class TestRecordFieldOperations:
    """Test adding and retrieving fields from records."""
    
    def test_add_field_to_record(self):
        """Test adding a field to a record."""
        leader = Leader()
        record = Record(leader)
        
        field = Field('245', '1', '0')
        field.add_subfield('a', 'Test Title')
        record.add_field(field)
        
        fields = record.fields_by_tag('245')
        assert len(fields) == 1
    
    def test_get_fields_by_tag(self):
        """Test retrieving fields by tag."""
        leader = Leader()
        record = Record(leader)
        
        # Add multiple 650 fields
        for i in range(3):
            field = Field('650', ' ', '0')
            field.add_subfield('a', f'Subject {i}')
            record.add_field(field)
        
        fields = record.fields_by_tag('650')
        assert len(fields) == 3
    
    def test_get_all_fields(self):
        """Test getting all fields from a record."""
        leader = Leader()
        record = Record(leader)
        
        for i in range(2):
            field = Field('245', '1', '0')
            field.add_subfield('a', f'Title {i}')
            record.add_field(field)
        
        all_fields = record.fields()
        assert len(all_fields) >= 2


class TestConvenienceMethods:
    """Test pymarc-style convenience methods."""
    
    def test_title_method(self):
        """Test the title() convenience method."""
        leader = Leader()
        record = Record(leader)
        
        field = Field('245', '1', '0')
        field.add_subfield('a', 'The pragmatic programmer :')
        field.add_subfield('b', 'from journeyman to master /')
        record.add_field(field)
        
        title = record.title
        assert title is not None
        assert 'pragmatic' in title.lower()
    
    def test_author_method(self):
        """Test the author() convenience method."""
        leader = Leader()
        record = Record(leader)
        record.add_field(create_field('100', '1', ' ', a='Hunt, Andrew'))
        
        author = record.author
        assert author is not None
        assert 'Hunt' in author
    
    def test_isbn_method(self):
        """Test the isbn() convenience method."""
        leader = Leader()
        record = Record(leader)
        record.add_field(create_field('020', ' ', ' ', a='0201616165'))
        
        isbn = record.isbn
        assert isbn is not None
        assert '0201616165' in isbn
    
    def test_subjects_method(self):
        """Test the subjects() convenience method with multiple 6xx field types."""
        leader = Leader()
        record = Record(leader)

        # Add 650 — Topical Term
        for i in range(3):
            record.add_field(create_field('650', ' ', '0', a=f'Subject {i}'))

        # Add 600 — Personal Name Subject
        record.add_field(create_field('600', '1', '0', a='Maimonides, Moses,'))

        # Add 630 — Uniform Title Subject
        record.add_field(create_field('630', '0', '4', a='Talmud Bavli.'))

        # Add 655 — Genre/Form
        record.add_field(create_field('655', ' ', '7', a='Commentaries.'))

        subjects = record.subjects
        assert len(subjects) == 6
        assert 'Subject 0' in subjects
        assert 'Maimonides, Moses,' in subjects
        assert 'Talmud Bavli.' in subjects
        assert 'Commentaries.' in subjects
    
    def test_location_method(self):
        """Test the location() convenience method."""
        leader = Leader()
        record = Record(leader)
        record.add_field(create_field('852', ' ', ' ', a='Main Library'))
        
        locations = record.location
        assert len(locations) >= 1
        assert 'Main Library' in locations
    
    def test_notes_method(self):
        """Test the notes() convenience method."""
        leader = Leader()
        record = Record(leader)
        record.add_field(create_field('500', ' ', ' ', a='General note'))
        
        notes = record.notes
        assert len(notes) >= 1
        assert 'General note' in notes
    
    def test_uniform_title_method(self):
        """Test the uniform_title() convenience method."""
        leader = Leader()
        record = Record(leader)
        record.add_field(create_field('130', ' ', '0', a='Standardized Title'))
        
        uniform_title = record.uniform_title
        assert uniform_title is not None
        assert uniform_title == 'Standardized Title'
    
    def test_sudoc_method(self):
        """Test the sudoc() convenience method."""
        leader = Leader()
        record = Record(leader)
        record.add_field(create_field('086', ' ', ' ', a='I 19.2:En 3'))
        
        sudoc = record.sudoc
        assert sudoc is not None
        assert sudoc == 'I 19.2:En 3'
    
    def test_issn_title_method(self):
        """Test the issn_title() convenience method."""
        leader = Leader()
        record = Record(leader)
        record.add_field(create_field('222', ' ', ' ', a='Key Title'))
        
        issn_title = record.issn_title
        assert issn_title is not None
        assert issn_title == 'Key Title'
    
    def test_issnl_method(self):
        """Test the issnl() convenience method for ISSN-L."""
        leader = Leader()
        record = Record(leader)
        record.add_field(create_field('024', ' ', ' ', a='1234-5678'))
        
        issnl = record.issnl
        assert issnl is not None
        assert issnl == '1234-5678'
    
    def test_pubyear_method(self):
        """Test the pubyear() convenience method (alias for publication_year)."""
        leader = Leader()
        record = Record(leader)
        
        # Add publication info
        field = Field('260', ' ', ' ')
        field.add_subfield('a', 'New York :')
        field.add_subfield('b', 'Penguin,')
        field.add_subfield('c', '2023')
        record.add_field(field)
        
        # pubyear() should work as alias
        year = record.pubyear
        assert year is not None
        assert year == '2023'
    
    def test_publisher_method(self):
        """Test the publisher() convenience method."""
        leader = Leader()
        record = Record(leader)
        
        field = Field('260', ' ', ' ')
        field.add_subfield('a', 'New York :')
        field.add_subfield('b', 'Addison-Wesley,')
        field.add_subfield('c', '2000')
        record.add_field(field)
        
        publisher = record.publisher
        assert publisher is not None
        assert 'Addison-Wesley' in publisher
    
    def test_issn_method(self):
        """Test the issn() convenience method."""
        leader = Leader()
        record = Record(leader)
        record.add_field(create_field('022', ' ', ' ', a='0028-0836'))
        
        issn = record.issn
        assert issn is not None
        assert issn == '0028-0836'
    
    def test_series_method(self):
        """Test the series() convenience method."""
        leader = Leader()
        record = Record(leader)
        record.add_field(create_field('490', ' ', ' ', a='Programming Patterns'))
        
        series = record.series
        assert series is not None
        assert 'Programming' in series
    
    def test_physical_description_method(self):
        """Test the physical_description() convenience method."""
        leader = Leader()
        record = Record(leader)
        
        field = Field('300', ' ', ' ')
        field.add_subfield('a', '256 pages ;')
        field.add_subfield('c', '24 cm')
        record.add_field(field)
        
        phys_desc = record.physical_description
        assert phys_desc is not None
        assert '256' in phys_desc


class TestFieldDictLike:
    """Test dictionary-like access to fields (pymarc style)."""
    
    def test_field_dict_access_not_implemented(self):
        """
        NOTE: This tests a missing feature.
        pymarc allows: record['245']['a']
        mrrc currently does not support this syntax.
        """
        leader = Leader()
        record = Record(leader)
        
        field = Field('245', '1', '0')
        field.add_subfield('a', 'Test Title')
        record.add_field(field)
        
        # This should work in pymarc but doesn't in mrrc yet
        try:
            title_a = record['245']['a']
            assert title_a == 'Test Title'
        except (TypeError, KeyError):
            pytest.skip("Dictionary-like field access not yet implemented")


class TestFieldSubfieldDict:
    """Test subfield access patterns."""
    
    def test_get_subfield_values(self):
        """Test getting subfield values from a field."""
        field = Field('245', '1', '0')
        field.add_subfield('a', 'value_a')
        field.add_subfield('b', 'value_b')
        field.add_subfield('c', 'value_c')
        
        # This pattern works in pymarc
        # field.get_subfields('a', 'b')
        # We need to test what mrrc currently supports
        assert len(field.subfields()) == 3


class TestRecordEquality:
    """Test record equality comparison."""
    
    def test_record_equality(self):
        """Test comparing two identical records."""
        leader1 = Leader()
        record1 = Record(leader1)
        record1.add_control_field('001', 'test-id')
        
        leader2 = Leader()
        record2 = Record(leader2)
        record2.add_control_field('001', 'test-id')
        
        # Equality should work
        assert record1 == record2


class TestRecordSerialization:
    """Test converting records to various formats."""
    
    def test_to_json(self):
        """Test JSON serialization."""
        leader = Leader()
        record = Record(leader)
        record.add_control_field('001', 'test-id')
        
        json_str = record.to_json()
        assert json_str is not None
        assert '001' in json_str or 'test-id' in json_str
    
    def test_to_xml(self):
        """Test XML serialization."""
        leader = Leader()
        record = Record(leader)
        record.add_control_field('001', 'test-id')
        
        xml_str = record.to_xml()
        assert xml_str is not None
        assert 'xml' in xml_str.lower() or '<' in xml_str
    
    def test_to_dublin_core(self):
        """Test Dublin Core serialization."""
        leader = Leader()
        record = Record(leader)
        
        # Add title
        field = Field('245', '1', '0')
        field.add_subfield('a', 'Test Title')
        record.add_field(field)
        
        dc = record.to_dublin_core()
        assert isinstance(dc, dict)
        assert 'title' in dc
    
    def test_to_marc21(self):
        """Test MARC21 binary serialization."""
        leader = Leader()
        record = Record(leader)
        record.add_control_field('001', 'test-id-001')
        
        # Add a title field
        field = Field('245', '1', '0')
        field.add_subfield('a', 'Test Title')
        record.add_field(field)
        
        # Serialize to MARC21
        marc_bytes = record.to_marc21()
        
        # Verify it's bytes and not empty
        assert isinstance(marc_bytes, bytes)
        assert len(marc_bytes) > 0
        
        # MARC21 should start with a leader (24 bytes)
        assert len(marc_bytes) >= 24
        
        # Verify it can be read back
        reader = MARCReader(io.BytesIO(marc_bytes))
        read_record = reader.read_record()
        assert read_record is not None
        assert read_record.control_field('001') == 'test-id-001'


class TestReadingFromFile:
    """Test reading MARC records from file."""
    
    def test_marc_reader_basic(self, fixture_1k):
        """Test basic MARCReader functionality."""
        data = io.BytesIO(fixture_1k)
        reader = MARCReader(data)
        
        count = 0
        while record := reader.read_record():
            assert record is not None
            count += 1
            if count >= 5:  # Just test first 5
                break
        
        assert count > 0


class TestIndicators:
    """Test field indicators."""
    
    def test_field_indicators(self):
        """Test setting and getting field indicators."""
        # pymarc allows: indicators=['1', '0']
        # mrrc uses positional args: Field(tag, ind1, ind2)
        field = Field('245', '1', '0')
        field.add_subfield('a', 'Test')
        assert field.tag == '245'
        assert field.indicator1 == '1'
        assert field.indicator2 == '0'


if __name__ == '__main__':
    pytest.main([__file__, '-v'])