from collections import defaultdict
from typing import List, Optional, Tuple
from unittest import TestCase
from eyecite import get_citations
from eyecite.models import FullCitation, Resource
from eyecite.resolve import resolve_citations
def format_resolution(resolution):
return {
k.citation.matched_text(): [i.matched_text() for i in v]
for k, v in resolution.items()
}
class ResolveTest(TestCase):
maxDiff = None
def assertResolution(self, citations, expected_resolution_dict):
actual_resolution_dict = resolve_citations(citations)
self.assertEqual(
format_resolution(actual_resolution_dict),
format_resolution(expected_resolution_dict),
)
def checkResolution(
self, *expected_resolutions: Tuple[Optional[int], str]
):
expected_resolution_dict = defaultdict(list)
citations = []
resources: List[Resource] = []
for i, cite_text in expected_resolutions:
cites = get_citations(cite_text)
self.assertEqual(
len(cites),
1,
f"Failed to find exactly one cite in {repr(cite_text)}",
)
cite = cites[0]
citations.append(cite)
if i is None:
continue
if i > len(resources):
self.fail(
f"Invalid row {repr((i, cite_text))}: target index {i} is too high."
)
if i == len(resources):
if not isinstance(cite, FullCitation):
self.fail(
f"Invalid row {repr((i, cite_text))}: first instance of {i} must be a full cite."
)
resources.append(Resource(citation=cite))
expected_resolution_dict[resources[i]].append(cites[0])
self.assertResolution(citations, expected_resolution_dict)
def test_full_resolution(self):
self.checkResolution(
(0, "Foo v. Bar, 1 U.S. 1."),
)
self.checkResolution(
(0, "Foo v. Bar, 1 U.S. 1."),
(0, "Foo v. Bar, 1 U.S. 1."),
)
self.checkResolution(
(0, "Foo v. Bar, 1 U.S. 1."),
(1, "Smith v. Jones, 1 F.2d 1."),
)
self.checkResolution(
(0, "Foo v. Bar, 1 U.S. 1."),
(0, "Foo v. Bar, 1 U.S. 1."),
(0, "Foo, 1 U.S., at 2."),
(0, "Foo, supra at 2."),
)
def test_supra_resolution(self):
self.checkResolution(
(0, "Foo v. Bar, 1 U.S. 1."),
(0, "Foo, supra, at 2."),
)
self.checkResolution(
(0, "Foo v. Bar, 1 U.S. 1."),
(1, "Foo v. Bar, 1 U.S. 2."),
(None, "Foo, supra, at 2."),
)
def test_short_resolution(self):
self.checkResolution(
(0, "Foo v. Bar, 1 U.S. 1."),
(0, "1 U.S., at 2."),
)
self.checkResolution(
(0, "Foo v. Bar, 1 U.S. 1."),
(0, "Foo, 1 U.S., at 2."),
)
self.checkResolution(
(0, "Foo v. Bar, 1 U.S. 1."),
(1, "Wrong v. Wrong, 1 U.S. 2."),
(0, "Foo, 1 U.S., at 2."),
)
self.checkResolution(
(0, "Foo v. Bar, 1 U.S. 1."),
(1, "Boo v. Far, 1 U.S. 2."),
(None, "1 U.S., at 2."),
)
self.checkResolution(
(0, "Foo v. Bar, 1 U.S. 1."),
(1, "Foo v. Bar, 1 U.S. 2."),
(None, "Foo, 1 U.S., at 2."),
)
self.checkResolution(
(0, "Foo v. Bar, 1 U.S. 1."),
(None, "2 F.2d, at 2."),
)
def test_ambigous_short_cite(self):
self.checkResolution(
(0, "Foo v. Bar, 1 U.S. 1."),
(1, "See Foo, 1 U.S. 2."),
(None, "Foo, 1 U.S., at 2."),
)
def test_id_resolution(self):
self.checkResolution(
(0, "Foo v. Bar, 1 U.S. 1."),
(0, "Id."),
(0, "Id. at 2."),
)
self.checkResolution(
(0, "Foo v. Bar, 1 U.S. 1."),
(None, "2 F.2d, at 2."),
(None, "Id. at 2."),
)
self.checkResolution(
(0, "Foo v. Bar, 1 U.S. 1."),
(None, "Blah § 2."),
(None, "Id. at 2."),
)
self.checkResolution(
(None, "Id. at 2."),
)
self.checkResolution(
(0, "Foo v. Bar, 1 U.S. 100."),
(None, "Id. at 500."),
(0, "Foo v. Bar, 1 U.S. 100."), (None, "Id. at 50."),
(0, "Foo v. Bar, 1 U.S. 100."), (None, "Id. at ¶ 100."),
(1, "Ala. Code § 92"),
(1, "Id. at 2000"),
)
def test_non_case_resolution(self):
self.checkResolution(
(0, "Foo v. Bar, 1 U.S. 1."),
(0, "Id. at 2."),
(1, "Mass. Gen. Laws ch. 1, § 2"),
(1, "Id."),
(0, "Foo, supra, at 2."),
(2, "1 Minn. L. Rev. 1."),
(2, "Id. at 2."),
(0, "Foo, 1 U.S., at 2."),
)
def test_complex_resolution(self):
self.checkResolution(
(0, "Blah blah Foo v. Bar 1 U.S. 1, 77 blah blah."),
(1, "Asdf asdf Qwerty v. Uiop 2 F.3d 500, 555."),
(0, "Also check out Foo, 1 U.S. at 99."),
(1, "Then let's cite Qwerty, supra, at 567."),
(0, "See also Foo, supra, at 101 as well."),
(2, "Another full citation is Lorem v. Ipsum 1 U. S. 50."),
(1, "Quoting Qwerty, “something something”, 2 F.3d 500, at 559."),
(None, "This case is similar to Fake, supra,"),
(1, "and Qwerty supra, as well."),
(1, "This should resolve to the foregoing. Ibid."),
(1, "This should also convert appropriately, see Id. at 567."),
(
None,
"But this fails because the pin cite is too low, see Id. at 400.",
),
(
None,
"This should fail to resolve because the reporter and citation is ambiguous, 1 U. S., at 51.",
),
(2, "However, this should succeed, Lorem, 1 U.S., at 52."),
)