Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions test/test_article.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# encoding=utf-8
import unittest
from unittest.mock import Mock, patch, MagicMock
from datetime import datetime
import pytz
from collections import OrderedDict

from ukbot.article import Article
from ukbot.revision import Revision


class TestArticle(unittest.TestCase):

def setUp(self):
# Create a complete mock setup
self.site_mock = Mock()
self.site_mock.key = 'test.wikipedia.org'
self.site_mock.match_prefix = Mock(return_value=False)
self.site_mock.link_to = Mock(side_effect=lambda article: f'{article.site().key}:{article.name}')

# Mock pages dict for MediaWiki API calls
page_mock = Mock()
page_mock.revisions = Mock(return_value=iter([{'timestamp': (2020, 1, 1, 0, 0, 0, 0, 0, 0)}]))
self.site_mock.pages = {'Test Article': page_mock}

self.contest_mock = Mock()
self.contest_mock.sql = Mock()
self.contest_mock.sql.cursor = Mock()
self.contest_mock.sql.commit = Mock()

self.user_mock = Mock()
self.user_mock.name = 'TestUser'
self.user_mock.point_deductions = []
self.user_mock.revisions = OrderedDict()
self.user_mock.contest = Mock(return_value=self.contest_mock)

self.article = Article(self.site_mock, self.user_mock, 'Test Article', ns='0')

def test_init(self):
"""Test Article initialization"""
self.assertEqual(self.article.name, 'Test Article')
self.assertEqual(self.article.ns, '0')
self.assertEqual(self.article.disqualified, False)
self.assertIsInstance(self.article.revisions, OrderedDict)
self.assertEqual(len(self.article.errors), 0)

def test_key_property(self):
"""Test article key generation"""
expected_key = 'test.wikipedia.org:Test Article'
self.assertEqual(self.article.key, expected_key)

def test_site_method(self):
"""Test site() method returns the site"""
self.assertEqual(self.article.site(), self.site_mock)

def test_user_method(self):
"""Test user() method returns the user"""
self.assertEqual(self.article.user(), self.user_mock)

def test_add_revision(self):
"""Test adding a revision to an article"""
rev = self.article.add_revision(123, timestamp=1234567890, username='TestUser')

self.assertIsInstance(rev, Revision)
self.assertEqual(len(self.article.revisions), 1)
self.assertIn(123, self.article.revisions)
self.assertEqual(self.article.revisions[123], rev)

def test_firstrev(self):
"""Test firstrev property returns first revision"""
rev1 = self.article.add_revision(100, timestamp=1000000000, username='TestUser')
rev2 = self.article.add_revision(200, timestamp=2000000000, username='TestUser')

self.assertEqual(self.article.firstrev, rev1)

def test_lastrev(self):
"""Test lastrev property returns last revision"""
rev1 = self.article.add_revision(100, timestamp=1000000000, username='TestUser')
rev2 = self.article.add_revision(200, timestamp=2000000000, username='TestUser')

self.assertEqual(self.article.lastrev, rev2)

def test_new_property_with_new_page(self):
"""Test new property returns True for new pages"""
rev = self.article.add_revision(100, timestamp=1000000000, username='TestUser')
rev.parentid = 0

self.assertTrue(self.article.new)

def test_new_property_with_existing_page(self):
"""Test new property returns False for existing pages"""
rev = self.article.add_revision(100, timestamp=1000000000, username='TestUser')
rev.parentid = 50

self.assertFalse(self.article.new)

def test_redirect_property(self):
"""Test redirect property"""
rev = self.article.add_revision(100, timestamp=1000000000, username='TestUser')
rev.text = '#REDIRECT [[Target]]'

self.site_mock.redirect_regexp = Mock()
self.site_mock.redirect_regexp.search = Mock(return_value=True)

self.assertTrue(self.article.redirect)

def test_new_non_redirect(self):
"""Test new_non_redirect property"""
rev = self.article.add_revision(100, timestamp=1000000000, username='TestUser')
rev.parentid = 0 # Makes it a new page
rev.text = 'Normal content'

# Mock redirect_regexp.match() to return None (no redirect)
redirect_mock = Mock()
redirect_mock.match = Mock(return_value=None)
self.site_mock.redirect_regexp = redirect_mock

self.assertTrue(self.article.new_non_redirect)

def test_bytes_property(self):
"""Test bytes property calculation"""
rev1 = self.article.add_revision(100, timestamp=1000000000, username='TestUser')
rev1.size = 200
rev1.parentsize = 50
rev2 = self.article.add_revision(200, timestamp=2000000000, username='TestUser')
rev2.size = 275
rev2.parentsize = 200

# rev1.bytes = 200-50 = 150, rev2.bytes = 275-200 = 75, total = 225
self.assertEqual(self.article.bytes, 225)

def test_words_property(self):
"""Test words property calculation"""
rev1 = self.article.add_revision(100, timestamp=1000000000, username='TestUser')
rev1.text = 'one two three four five six seven' # 7 words
rev1.parenttext = ''
rev2 = self.article.add_revision(200, timestamp=2000000000, username='TestUser')
rev2.text = 'one two three four five six seven eight nine ten' # 10 words
rev2.parenttext = 'one two three four five six seven' # 7 words, so 3 new words

# rev1 adds 7 words, rev2 adds 3 words, total = 10
self.assertEqual(self.article.words, 10)

def test_link_method(self):
"""Test link() method generates correct link"""
expected_link = 'test.wikipedia.org:Test Article'
self.assertEqual(self.article.link(), expected_link)

def test_created_at_property(self):
"""Test created_at property"""
# Test when _created_at is set
test_time = pytz.utc.localize(datetime(2020, 1, 1, 12, 0, 0))
self.article._created_at = test_time
self.assertEqual(self.article.created_at, test_time)

def test_created_at_from_firstrev(self):
"""Test created_at falls back to firstrev timestamp"""
rev = self.article.add_revision(100, timestamp=1577836800.0, username='TestUser') # 2020-01-01

self.assertIsNotNone(self.article.created_at)
self.assertIsInstance(self.article.created_at, datetime)

def test_eq_method(self):
"""Test __eq__ method"""
article2 = Article(self.site_mock, self.user_mock, 'Test Article', ns='0')
article3 = Article(self.site_mock, self.user_mock, 'Different Article', ns='0')

self.assertEqual(self.article, article2)
self.assertNotEqual(self.article, article3)

def test_repr_and_str(self):
"""Test __repr__ and __str__ methods"""
expected = 'Article(test.wikipedia.org, Test Article, TestUser)'
self.assertEqual(repr(self.article), expected)
self.assertEqual(str(self.article), expected)

def test_hash(self):
"""Test __hash__ method"""
article2 = Article(self.site_mock, self.user_mock, 'Test Article', ns='0')

self.assertEqual(hash(self.article), hash(article2))


if __name__ == '__main__':
unittest.main()
186 changes: 186 additions & 0 deletions test/test_contributions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# encoding=utf-8
import unittest
from unittest.mock import Mock
from datetime import datetime
import pytz

from ukbot.contributions import UserContribution, UserContributions


class TestUserContribution(unittest.TestCase):

def setUp(self):
# Create mocks with specific return values
article_mock = Mock(name='Test Article')
site_mock = Mock(key='test.wiki')
user_mock = Mock(name='TestUser')

article_mock.site = Mock(return_value=site_mock)
article_mock.user = Mock(return_value=user_mock)

self.rev = Mock()
self.rev.revid = 12345
self.rev.article = Mock(return_value=article_mock)

self.rule = Mock()
self.rule.__class__.__name__ = 'TestRule'

self.contribution = UserContribution(
rev=self.rev,
points=10.5,
rule=self.rule,
description='Test contribution'
)

def test_init(self):
"""Test UserContribution initialization"""
self.assertEqual(self.contribution.rev, self.rev)
self.assertEqual(self.contribution.points, 10.5)
self.assertEqual(self.contribution.rule, self.rule)
self.assertEqual(self.contribution.description, 'Test contribution')

def test_is_negative_false(self):
"""Test is_negative returns False for positive points"""
self.assertFalse(self.contribution.is_negative())

def test_is_negative_true(self):
"""Test is_negative returns True for negative points"""
negative_contrib = UserContribution(
rev=self.rev,
points=-5.0,
rule=self.rule,
description='Negative'
)
self.assertTrue(negative_contrib.is_negative())

def test_article_property(self):
"""Test article property returns article"""
self.assertEqual(self.contribution.article, self.rev.article())

def test_site_property(self):
"""Test site property returns site"""
self.assertEqual(self.contribution.site, self.rev.article().site())

def test_user_property(self):
"""Test user property returns user"""
self.assertEqual(self.contribution.user, self.rev.article().user())


class TestUserContributions(unittest.TestCase):

def setUp(self):
self.user = Mock()
self.user.name = 'TestUser'
self.user.point_deductions = []
self.user.suspended_since = None
self.user.disqualified_articles = []

self.config = {
'point_caps': {},
'wikidata_languages': ['en', 'es', 'fi']
}

self.contributions = UserContributions(self.user, self.config)

def test_init(self):
"""Test UserContributions initialization"""
# Note: self.contributions.user is a weakref, so we need to call it
self.assertEqual(self.contributions.user(), self.user)
self.assertEqual(self.contributions.wikidata_languages, ['en', 'es', 'fi'])
self.assertEqual(len(self.contributions.contributions), 0)

def test_add_contribution(self):
"""Test adding a contribution"""
article_mock = Mock(name='Article')
rev = Mock()
rev.revid = 100
rev.article = Mock(return_value=article_mock)

rule = Mock()
rule.maxpoints = None # No capping

contrib = UserContribution(
rev=rev,
points=10,
rule=rule,
description='Test'
)

self.contributions.add(contrib)

self.assertEqual(len(self.contributions.contributions), 1)
self.assertIn(contrib, self.contributions.contributions)

def test_get_all_contributions(self):
"""Test getting all contributions"""
article_mock = Mock(name='Article')
rev = Mock()
rev.revid = 100
rev.article = Mock(return_value=article_mock)

rule = Mock()
rule.maxpoints = None # No capping

contrib1 = UserContribution(rev=rev, points=10, rule=rule, description='Test1')
contrib2 = UserContribution(rev=rev, points=5, rule=rule, description='Test2')

self.contributions.add(contrib1)
self.contributions.add(contrib2)

all_contribs = self.contributions.get()
self.assertEqual(len(all_contribs), 2)

def test_get_contributions_by_revision(self):
"""Test getting contributions by revision"""
article1_mock = Mock(name='Article1')
rev1 = Mock()
rev1.revid = 100
rev1.article = Mock(return_value=article1_mock)

article2_mock = Mock(name='Article2')
rev2 = Mock()
rev2.revid = 200
rev2.article = Mock(return_value=article2_mock)

rule = Mock()
rule.maxpoints = None # No capping

contrib1 = UserContribution(rev=rev1, points=10, rule=rule, description='Test1')
contrib2 = UserContribution(rev=rev2, points=5, rule=rule, description='Test2')

self.contributions.add(contrib1)
self.contributions.add(contrib2)

rev1_contribs = self.contributions.get(revision=rev1)
self.assertEqual(len(rev1_contribs), 1)
self.assertEqual(rev1_contribs[0], contrib1)

def test_sum_contributions(self):
"""Test summing all contribution points"""
article_mock = Mock(name='Article')
article_mock.key = 'test:Article'

rev = Mock()
rev.revid = 100
rev.article = Mock(return_value=article_mock)
rev.utc = pytz.utc.localize(datetime(2020, 1, 1))
rev.point_deductions = []

article_mock.revisions = {100: rev}

rule = Mock()
rule.maxpoints = None # No capping

contrib1 = UserContribution(rev=rev, points=10, rule=rule, description='Test1')
contrib2 = UserContribution(rev=rev, points=5.5, rule=rule, description='Test2')

self.contributions.add(contrib1)
self.contributions.add(contrib2)

# Sum using get_article_points
total = self.contributions.get_article_points(article_mock)
self.assertEqual(total, 15.5)


if __name__ == '__main__':
unittest.main()
Loading