wikimedia/pywikibot

View on GitHub
tests/link_tests.py

Summary

Maintainability
F
1 wk
Test Coverage
#!/usr/bin/env python3
"""Test Link functionality."""
#
# (C) Pywikibot team, 2014-2024
#
# Distributed under the terms of the MIT license.
#
from __future__ import annotations

import re
from contextlib import suppress

import pywikibot
from pywikibot import Site, config
from pywikibot.exceptions import InvalidTitleError, SiteDefinitionError
from pywikibot.page import Link, Page, SiteLink
from pywikibot.site import Namespace
from tests.aspects import (
    AlteredDefaultSiteTestCase,
    DefaultDrySiteTestCase,
    TestCase,
    WikimediaDefaultSiteTestCase,
    unittest,
)


class TestCreateSeparated(DefaultDrySiteTestCase):

    """Test ``Link.create_separated``."""

    def _test_link(self, link, page, section, label):
        """Test the separate contents of the link."""
        self.assertIs(link.site, self.site)
        self.assertEqual(link.title, page)
        if section is None:
            self.assertIsNone(link.section)
        else:
            self.assertEqual(link.section, section)
        if label is None:
            self.assertIsNone(link.anchor)
        else:
            self.assertEqual(link.anchor, label)

    def test(self):
        """Test combinations of parameters."""
        self._test_link(Link.create_separated('Foo', self.site),
                        'Foo', None, None)
        self._test_link(Link.create_separated('Foo', self.site, section='Bar'),
                        'Foo', 'Bar', None)
        self._test_link(Link.create_separated('Foo', self.site, label='Baz'),
                        'Foo', None, 'Baz')
        self._test_link(Link.create_separated('Foo', self.site, section='Bar',
                                              label='Baz'),
                        'Foo', 'Bar', 'Baz')


# ---- Tests checking if the parser does (not) accept (in)valid titles


class TestLink(DefaultDrySiteTestCase):

    """Test parsing links with DrySite.

    The DrySite is using the builtin namespaces which behaviour is controlled
    in this repository so namespace aware tests do work, even when the actual
    default site is using completely different namespaces.
    """

    def replaced(self, iterable):
        """Replace family specific title delimiter."""
        for items in iterable:
            if isinstance(items, str):
                items = [items]
            items = [re.sub(' ',
                            self.site.family.title_delimiter_and_aliases[0],
                            item)
                     for item in items]
            if len(items) == 1:
                items = items[0]
            yield items

    def test_valid(self):
        """Test that valid titles are correctly normalized."""
        title_tests = ['Sandbox', 'A "B"', "A 'B'", '.com', '~', '"', "'",
                       'Foo/.../Sandbox', 'Sandbox/...', 'A~~', 'X' * 252]

        extended_title_tests = [
            ('Talk:Sandbox', 'Sandbox'),
            ('Talk:Foo:Sandbox', 'Foo:Sandbox'),
            ('File:Example.svg', 'Example.svg'),
            ('File_talk:Example.svg', 'Example.svg'),
            (':A', 'A'),
            # Length is 256 total, but only title part matters
            ('Category:' + 'X' * 248, 'X' * 248),
            ('A%20B', 'A B'),
            ('A é B', 'A é B'),
            ('A é B', 'A é B'),
            ('A é B', 'A é B'),
            ('A   B', 'A B'),
            ('A   B', 'A B'),
        ]

        site = self.site

        for title in self.replaced(title_tests):
            with self.subTest(title=title):
                self.assertEqual(Link(title, site).title, title)

        for link, title in self.replaced(extended_title_tests):
            with self.subTest(link=link, title=title):
                self.assertEqual(Link(link, site).title, title)

        anchor_link = Link('A | B', site)
        self.assertEqual(anchor_link.title, 'A')
        self.assertEqual(anchor_link.anchor, ' B')

        section_link = Link('A%23B', site)
        self.assertEqual(section_link.title, 'A')
        self.assertEqual(section_link.section, 'B')

    def test_invalid(self):
        """Test that invalid titles raise InvalidTitleError."""
        # Bad characters forbidden regardless of wgLegalTitleChars
        def generate_contains_illegal_chars_exc_regex(text):
            exc_regex = (rf'^(u|)\'{re.escape(text)}\' contains illegal char'
                         rf'\(s\) (u|)\'{re.escape(text[2])}\'$')
            return exc_regex

        # Directory navigation
        def generate_contains_dot_combinations_exc_regex(text):
            exc_regex = (rf'^\(contains \. / combinations\): (u|)'
                         rf'\'{re.escape(text)}\'$')
            return exc_regex

        # Tilde
        def generate_contains_tilde_exc_regex(text):
            exc_regex = rf'^\(contains ~~~\): (u|)\'{re.escape(text)}\'$'
            return exc_regex

        # Overlength
        def generate_overlength_exc_regex(text):
            exc_regex = rf'^\(over 255 bytes\): (u|)\'{re.escape(text)}\'$'
            return exc_regex

        # Namespace prefix without actual title
        def generate_has_no_title_exc_regex(text):
            exc_regex = rf'^(u|)\'{re.escape(text.strip())}\' has no title\.$'
            return exc_regex

        title_tests = [
            # Empty title
            (['', ':'],
             r'^The link \[\[.*\]\] does not contain a page title$'),

            (['A [ B', 'A ] B', 'A { B', 'A } B', 'A < B', 'A > B'],
             generate_contains_illegal_chars_exc_regex),

            # URL encoding
            # %XX is understood by wikimedia but not %XXXX
            (['A%2523B'],
             r'^(u|)\'A%23B\' contains illegal char\(s\) (u|)\'%23\'$'),

            # A link is invalid if their (non-)talk page would be in another
            # namespace than the link's "other" namespace
            (['Talk:File:Example.svg'],
             r"The \(non-\)talk page of 'Talk:File:Example.svg'"
             r' is a valid title in another namespace.'),

            (['.', '..', './Sandbox', '../Sandbox', 'Foo/./Sandbox',
              'Foo/../Sandbox', 'Sandbox/.', 'Sandbox/..'],
             generate_contains_dot_combinations_exc_regex),

            (['A ~~~ Name', 'A ~~~~ Signature', 'A ~~~~~ Timestamp'],
             generate_contains_tilde_exc_regex),

            ([('x' * 256), ('Invalid:' + 'X' * 248)],
             generate_overlength_exc_regex),

            (['Talk:'],
             generate_has_no_title_exc_regex),
        ]

        # Known issues with wikihow.
        if self.site.family.name != 'wikihow':
            title_tests.extend([
                (['Category: ', 'Category: #bar'],
                 generate_has_no_title_exc_regex),
                (['__  __', '  __  '],
                 r'^The link \[\[\]\] does not contain a page title$'),
            ])

        for texts_to_test, exception_regex in title_tests:
            for text in self.replaced(texts_to_test):
                with self.subTest(title=text):
                    if callable(exception_regex):
                        regex = exception_regex(text)
                    else:
                        regex = exception_regex
                    with self.assertRaisesRegex(InvalidTitleError, regex):
                        Link(text, self.site).parse()

    def test_relative(self):
        """Test that relative links are handled properly."""
        # Subpage
        page = Page(self.site, 'Foo')
        rel_link = Link('/bar', page)
        self.assertEqual(rel_link.title, 'Foo/bar')
        self.assertEqual(rel_link.site, self.site)
        # Subpage of Page with section
        page = Page(self.site, 'Foo#Baz')
        rel_link = Link('/bar', page)
        self.assertEqual(rel_link.title, 'Foo/bar')
        self.assertEqual(rel_link.site, self.site)
        # Non-subpage link text beginning with slash
        abs_link = Link('/bar', self.site)
        self.assertEqual(abs_link.title, '/bar')


class Issue10254TestCase(DefaultDrySiteTestCase):

    """Test T102461 (Python issue 10254)."""

    def test_no_change(self):
        """Test T102461 (Python issue 10254) is not encountered."""
        title = 'Li̍t-sṳ́'
        link = Link(title, self.site)
        self.assertEqual(link.title, 'Li̍t-sṳ́')


# ---- The first set of tests are explicit links, starting with a ':'.

class LinkTestCase(AlteredDefaultSiteTestCase):

    """Cached API test for link tests."""

    cache = True


class LinkTestWikiEn(LinkTestCase):

    """Link tests on wikipedia:en."""

    family = 'wikipedia'
    code = 'en'

    def setUp(self):
        """Setup tests."""
        super().setUp()
        config.mylang = 'en'
        config.family = 'wikipedia'


class TestPartiallyQualifiedExplicitLinkSameSiteParser(LinkTestWikiEn):

    """Link tests."""

    def test_partially_qualified_NS0_code(self):
        """Test ':wikipedia:Main Page' on enwp is namespace 4."""
        link = Link(':wikipedia:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 4)

    def test_partially_qualified_NS1_code(self):
        """Test ':wikipedia:Talk:Main Page' on enwp is namespace 4."""
        link = Link(':wikipedia:Talk:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Talk:Main Page')
        self.assertEqual(link.namespace, 4)

    def test_partially_qualified_NS0_family(self):
        """Test ':en:Main Page' on enwp is namespace 0."""
        link = Link(':en:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 0)

    def test_partially_qualified_NS1_family(self):
        """Test ':en:Talk:Main Page' on enwp is namespace 1."""
        link = Link(':en:Talk:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 1)


class TestPartiallyQualifiedExplicitLinkDifferentCodeParser(LinkTestWikiEn):

    """Link tests."""

    def test_partially_qualified_NS0_family(self):
        """Test ':en:Main Page' on dewp is namespace 0."""
        link = Link(':en:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 0)

    def test_partially_qualified_NS1_family(self):
        """Test ':en:Talk:Main Page' on dewp is namespace 1."""
        link = Link(':en:Talk:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 1)


class TestPartiallyQualifiedExplicitLinkDifferentFamilyParser(LinkTestCase):

    """Link tests."""

    family = 'wikipedia'
    code = 'en'

    def setUp(self):
        """Setup tests."""
        super().setUp()
        config.mylang = 'en'
        config.family = 'wikisource'

    def test_partially_qualified_NS0_code(self):
        """Test ':wikipedia:Main Page' on enws is namespace 0."""
        link = Link(':wikipedia:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 0)

    def test_partially_qualified_NS1_code(self):
        """Test ':wikipedia:Talk:Main Page' on enws is ns 1."""
        link = Link(':wikipedia:Talk:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 1)


class TestFullyQualifiedSameNamespaceFamilyParser(LinkTestCase):

    """Link tests."""

    family = 'wikipedia'
    code = 'en'

    def test_namespace_vs_family(self):
        """Test namespace is selected before family."""
        config.mylang = 'en'
        config.family = 'wikipedia'
        link = Link(':wikipedia:en:Main Page')
        link.parse()
        self.assertEqual(link.title, 'En:Main Page')
        self.assertEqual(link.namespace, 4)

        link = Link(':wikipedia:en:Talk:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'En:Talk:Main Page')
        self.assertEqual(link.namespace, 4)


class TestFullyQualifiedExplicitLinkSameFamilyParser(LinkTestWikiEn):

    """Link tests."""

    def test_fully_qualified_NS0_code(self):
        """Test ':en:wikipedia:Main Page' on enwp is namespace 4."""
        link = Link(':en:wikipedia:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 4)

    def test_fully_qualified_NS1_code(self):
        """Test ':en:wikipedia:Talk:Main Page' on enwp is namespace 4."""
        link = Link(':en:wikipedia:Talk:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Talk:Main Page')
        self.assertEqual(link.namespace, 4)


class TestFullyQualifiedLinkDifferentFamilyParser(LinkTestCase):

    """Test link to a different family with and without preleading colon."""

    family = 'wikipedia'
    code = 'en'

    PATTERN = '{colon}{first}:{second}:{title}'

    def setUp(self):
        """Setup tests."""
        super().setUp()
        config.mylang = 'en'
        config.family = 'wikisource'

    def test_fully_qualified_NS0(self):
        """Test that fully qualified link is in namespace 0."""
        family, code = 'wikipedia', 'en'
        for colon in ('', ':'):  # with or without preleading colon
            # switch code:family sequence en:wikipedia or wikipedia:en
            for first, second in [(family, code), (code, family)]:
                with self.subTest(colon=colon,
                                  site=f'{first}:{second}'):
                    link_title = self.PATTERN.format(colon=colon,
                                                     first=first,
                                                     second=second,
                                                     title='Main Page')
                    link = Link(link_title)
                    link.parse()
                    self.assertEqual(link.site, self.site)
                    self.assertEqual(link.title, 'Main Page')
                    self.assertEqual(link.namespace, 0)

    def test_fully_qualified_NS1(self):
        """Test that fully qualified link is in namespace 1."""
        family, code = 'wikipedia', 'en'
        for colon in ('', ':'):  # with or without preleading colon
            # switch code:family sequence en:wikipedia or wikipedia:en
            for first, second in [(family, code), (code, family)]:
                with self.subTest(colon=colon,
                                  site=f'{first}:{second}'):
                    link_title = self.PATTERN.format(colon=colon,
                                                     first=first,
                                                     second=second,
                                                     title='Talk:Main Page')
                    link = Link(link_title)
                    link.parse()
                    self.assertEqual(link.site, self.site)
                    self.assertEqual(link.title, 'Main Page')
                    self.assertEqual(link.namespace, 1)


class TestFullyQualifiedExplicitLinkNoLangConfigFamilyParser(LinkTestCase):

    """Test link from family without lang code to a different family."""

    family = 'wikipedia'
    code = 'en'

    def setUp(self):
        """Setup tests."""
        super().setUp()
        config.mylang = 'wikidata'
        config.family = 'wikidata'

    def test_fully_qualified_NS0_code(self):
        """Test ':en:wikipedia:Main Page' on wikidata is namespace 4."""
        link = Link(':en:wikipedia:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 4)

    def test_fully_qualified_NS1_code(self):
        """Test ':en:wikipedia:Talk:Main Page' on wikidata is namespace 4."""
        link = Link(':en:wikipedia:Talk:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Talk:Main Page')
        self.assertEqual(link.namespace, 4)

    def test_fully_qualified_NS0_family(self):
        """Test ':wikipedia:en:Main Page' on wikidata is namespace 0."""
        link = Link(':wikipedia:en:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 0)

    def test_fully_qualified_NS1_family(self):
        """Test ':wikipedia:en:Talk:Main Page' on wikidata is namespace 1."""
        link = Link(':wikipedia:en:Talk:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 1)


class TestFullyQualifiedNoLangFamilyExplicitLinkParser(LinkTestCase):

    """Test wikibase links."""

    sites = {
        'wikidata': {
            'family': 'wikidata',
            'code': 'wikidata'
        },
        'test': {
            'family': 'wikipedia',
            'code': 'test'
        },
    }

    def setUp(self):
        """Setup tests."""
        super().setUp()
        config.mylang = 'en'
        config.family = 'wikipedia'

    def test_fully_qualified_NS_code(self):
        """Test ':testwiki:wikidata:Q6' on enwp."""
        for ns, title in enumerate(['Q6', 'Talk:Q6']):
            with self.subTest(title=title):
                link = Link(f':testwiki:wikidata:{title}')
                link.parse()
                self.assertEqual(link.site, self.get_site('wikidata'))
                self.assertEqual(link.title, 'Q6')
                self.assertEqual(link.namespace, ns)

    def test_fully_qualified_NS_family(self):
        """Test ':wikidata:testwiki:Q6' on enwp."""
        for ns, title in enumerate(['Q6', 'Talk:Q6']):
            with self.subTest(title=title):
                link = Link(f':wikidata:testwiki:{title}')
                link.parse()
                self.assertEqual(link.site, self.get_site('test'))
                self.assertEqual(link.title, 'Q6')
                self.assertEqual(link.namespace, ns)


class TestFullyQualifiedOneSiteFamilyExplicitLinkParser(LinkTestCase):

    """Test links to one site target family."""

    family = 'species'
    code = 'species'

    def setUp(self):
        """Setup tests."""
        super().setUp()
        config.mylang = 'en'
        config.family = 'wikipedia'

    def test_fully_qualified_NS0_code(self):
        """Test ':species:species:Main Page' on species is namespace 0."""
        link = Link(':species:species:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 0)

    def test_fully_qualified_NS1_code(self):
        """Test ':species:species:Talk:Main Page' on species is namespace 1."""
        link = Link(':species:species:Talk:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 1)


# ---- Tests of a Link without colons, which shouldn't be interwikis, follow.


class TestPartiallyQualifiedImplicitLinkSameSiteParser(LinkTestWikiEn):

    """Test partially qualified links to same site."""

    def test_partially_qualified_NS0_code(self):
        """Test 'wikipedia:Main Page' on enwp is namespace 4."""
        link = Link('wikipedia:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 4)

    def test_partially_qualified_NS1_code(self):
        """Test 'wikipedia:Talk:Main Page' on enwp is namespace 4."""
        link = Link('wikipedia:Talk:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Talk:Main Page')
        self.assertEqual(link.namespace, 4)

    def test_partially_qualified_NS0_family(self):
        """Test 'en:Main Page' on enwp is namespace 0."""
        link = Link('en:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 0)

    def test_partially_qualified_NS1_family(self):
        """Test 'en:Talk:Main Page' on enwp is namespace 1."""
        link = Link('en:Talk:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 1)


class TestPartiallyQualifiedImplicitLinkDifferentCodeParser(LinkTestWikiEn):

    """Test partially qualified links to different code."""

    def test_partially_qualified_NS0_family(self):
        """Test 'en:Main Page' on dewp is namespace 0."""
        link = Link('en:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 0)

    def test_partially_qualified_NS1_family(self):
        """Test 'en:Talk:Main Page' on dewp is namespace 1."""
        link = Link('en:Talk:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 1)


class TestPartiallyQualifiedImplicitLinkDifferentFamilyParser(LinkTestCase):

    """Test partially qualified links to different family."""

    family = 'wikipedia'
    code = 'en'

    def setUp(self):
        """Setup tests."""
        super().setUp()
        config.mylang = 'en'
        config.family = 'wikisource'

    def test_partially_qualified_NS0_code(self):
        """Test 'wikipedia:Main Page' on enws is namespace 0."""
        link = Link('wikipedia:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 0)

    def test_partially_qualified_NS1_code(self):
        """Test 'wikipedia:Talk:Main Page' on enws is ns 1."""
        link = Link('wikipedia:Talk:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 1)


class TestFullyQualifiedImplicitLinkSameFamilyParser(LinkTestWikiEn):

    """Link tests."""

    def test_fully_qualified_NS0_code(self):
        """Test 'en:wikipedia:Main Page' on enwp is namespace 4."""
        link = Link('en:wikipedia:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 4)

    def test_fully_qualified_NS1_code(self):
        """Test 'en:wikipedia:Talk:Main Page' on enwp is namespace 4."""
        link = Link('en:wikipedia:Talk:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Talk:Main Page')
        self.assertEqual(link.namespace, 4)


class TestFullyQualifiedImplicitLinkNoLangConfigFamilyParser(LinkTestCase):

    """Test implicit link from family without lang code to other family."""

    family = 'wikipedia'
    code = 'en'

    def setUp(self):
        """Setup tests."""
        super().setUp()
        config.mylang = 'wikidata'
        config.family = 'wikidata'

    def test_fully_qualified_NS0_code(self):
        """Test 'en:wikipedia:Main Page' on wikidata is namespace 4."""
        link = Link('en:wikipedia:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 4)

    def test_fully_qualified_NS1_code(self):
        """Test 'en:wikipedia:Talk:Main Page' on wikidata isn't namespace 1."""
        link = Link('en:wikipedia:Talk:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Talk:Main Page')
        self.assertEqual(link.namespace, 4)

    def test_fully_qualified_NS0_family(self):
        """Test 'wikipedia:en:Main Page' on wikidata is namespace 0."""
        link = Link('wikipedia:en:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.namespace, 0)
        self.assertEqual(link.title, 'Main Page')

    def test_fully_qualified_NS1_family(self):
        """Test 'wikipedia:en:Talk:Main Page' on wikidata is namespace 1."""
        link = Link('wikipedia:en:Talk:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 1)


class TestFullyQualifiedNoLangFamilyImplicitLinkParser(LinkTestCase):

    """Test wikibase links without preleading colon."""

    family = 'wikidata'
    code = 'test'

    def setUp(self):
        """Setup tests."""
        super().setUp()
        config.mylang = 'en'
        config.family = 'wikipedia'

    def test_fully_qualified_NS0(self):
        """Test prefixed links with 'Q6' on enwp is namespace 0."""
        test = [('testwiki:wikidata', 'wikidata:wikidata'),
                ('wikidata:testwiki', 'wikipedia:test')]
        for linkprefix, sitetitle in test:
            with self.subTest(pattern=linkprefix):
                link = Link(linkprefix + ':Q6')
                link.parse()
                self.assertEqual(link.site, pywikibot.Site(sitetitle))
                self.assertEqual(link.title, 'Q6')
                self.assertEqual(link.namespace, 0)

    def test_fully_qualified_NS1(self):
        """Test prefixed links with 'Talk:Q6' on enwp is namespace 1."""
        test = [('testwiki:wikidata', 'wikidata:wikidata'),
                ('wikidata:testwiki', 'wikipedia:test')]
        for linkprefix, sitetitle in test:
            with self.subTest(pattern=linkprefix):
                link = Link(linkprefix + ':Talk:Q6')
                link.parse()
                self.assertEqual(link.site, pywikibot.Site(sitetitle))
                self.assertEqual(link.title, 'Q6')
                self.assertEqual(link.namespace, 1)


class TestFullyQualifiedOneSiteFamilyImplicitLinkParser(LinkTestCase):

    """Test links to one site target family without preleading colon."""

    family = 'species'
    code = 'species'

    def setUp(self):
        """Setup tests."""
        super().setUp()
        config.mylang = 'en'
        config.family = 'wikipedia'

    def test_fully_qualified_NS0_family_code(self):
        """Test 'species:species:Main Page' on enwp is namespace 0."""
        link = Link('species:species:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 0)

    def test_fully_qualified_NS1_family_code(self):
        """Test 'species:species:Talk:Main Page' on enwp is namespace 1."""
        link = Link('species:species:Talk:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 1)

    def test_fully_qualified_NS0_code(self):
        """Test 'species:Main Page' on enwp is namespace 0."""
        link = Link('species:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 0)

    def test_fully_qualified_NS1_code(self):
        """Test 'species:Talk:Main Page' on enwp is namespace 1."""
        link = Link('species:Talk:Main Page')
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'Main Page')
        self.assertEqual(link.namespace, 1)


class TestEmptyTitle(TestCase):

    """Test links which contain no title."""

    family = 'wikipedia'
    code = 'en'

    def test_interwiki_mainpage(self):
        """Test that Link allow links without a title to the main page."""
        link = Link('en:', self.site)
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, '')
        self.assertEqual(link.namespace, 0)

    def test_interwiki_namespace_without_title(self):
        """Test that Link doesn't allow links without a title."""
        link = Link('en:Help:', self.site)
        with self.assertRaisesRegex(
                InvalidTitleError,
                "'en:Help:' has no title."):
            link.parse()

    def test_no_text(self):
        """Test that Link doesn't allow empty."""
        link = Link('', self.site)
        with self.assertRaisesRegex(
                InvalidTitleError,
                r'The link \[\[.*\]\] does not contain a page title'):
            link.parse()

    def test_namespace_lookalike(self):
        """Test that Link does only detect valid namespaces."""
        link = Link('CAT:', self.site)
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'CAT:')
        self.assertEqual(link.namespace, 0)

        link = Link('en:CAT:', self.site)
        link.parse()
        self.assertEqual(link.site, self.site)
        self.assertEqual(link.title, 'CAT:')
        self.assertEqual(link.namespace, 0)


class TestForeignInterwikiLinks(WikimediaDefaultSiteTestCase):

    """Test links to non-wikis."""

    family = 'wikipedia'
    code = 'en'

    def test_non_wiki_prefix(self):
        """Test that Link fails if the interwiki prefix is not a wiki.

        *bugzilla* does not return a json content but redirects to phab.
        api.Request._json_loads cannot detect this problem and raises a
        :exc:`exceptions.SiteDefinitionError`. The site is created
        anyway but the title cannot be parsed:
        """
        link = Link('bugzilla:1337', source=self.site)
        with self.assertRaises(SiteDefinitionError):
            link.site
        self.assertEqual(link.site.sitename, 'wikimedia:wikimedia')
        self.assertTrue(link._is_interwiki)

    def test_other_wiki_prefix(self):
        """Test that Link fails if the interwiki prefix is a unknown family.

        Sometimes *bulba* does not return a json content but a security
        script. api.Request._json_loads raises a
        :exc:`exceptions.SiteDefinitionError` for an invalid
        :class:`Autofamily('bulbapedia.bulbagarden.net')
        <family.AutoFamily>`. The site is created anyway but the title
        cannot be parsed in such case.
        """
        link = Link('bulba:title on auto-generated Site', source=self.site)
        try:
            link.site
        except SiteDefinitionError as e:  # pragma: no cover
            self.assertEqual(
                str(e), "Invalid AutoFamily('bulbapedia.bulbagarden.net')")
        else:
            self.assertEqual(link.title, 'Title on auto-generated Site')
        self.assertEqual(link.site.sitename, 'bulba:bulba')
        self.assertTrue(link._is_interwiki)

    def test_invalid_wiki_prefix(self):
        """Test that Link with prefix not listed in InterwikiMap."""
        title = 'Unknownprefix:This title'
        link = Link(title, source=self.site)
        self.assertEqual(link.title, title)
        self.assertEqual(link.site, self.site)
        self.assertFalse(link._is_interwiki)


class TestSiteLink(WikimediaDefaultSiteTestCase):

    """Test parsing namespaces when creating SiteLinks."""

    def _test_link(self, link, title, namespace, site_code, site_fam):
        """Test the separate contents of the link."""
        self.assertEqual(link.title, title)
        self.assertEqual(link.namespace, namespace)
        self.assertEqual(link.site, Site(site_code, site_fam))
        self.assertEqual(link.badges, [])

    def test_site_link(self):
        """Test parsing of title."""
        self._test_link(SiteLink('Foobar', 'enwiki'),
                        'Foobar', Namespace.MAIN, 'en', 'wikipedia')
        self._test_link(SiteLink('Mall:!!', 'svwiki'),
                        '!!', Namespace.TEMPLATE, 'sv', 'wikipedia')
        self._test_link(SiteLink('Vorlage:!!', 'dewikibooks'),
                        '!!', Namespace.TEMPLATE, 'de', 'wikibooks')
        self._test_link(SiteLink('Ai Weiwei: Never Sorry', 'enwiki'),
                        'Ai Weiwei: Never Sorry', Namespace.MAIN,
                        'en', 'wikipedia')


if __name__ == '__main__':
    with suppress(SystemExit):
        unittest.main()