mirror of
https://github.com/sissbruecker/linkding.git
synced 2026-03-10 20:03:12 +08:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c19266ef8 | ||
|
|
048a8b1162 | ||
|
|
2fb0bb1224 | ||
|
|
3e48b22095 | ||
|
|
9aa17d0528 | ||
|
|
d643fca98f | ||
|
|
f293fa15bc | ||
|
|
f58434077b | ||
|
|
59641e787c | ||
|
|
0d36a3bb86 |
3
.coveragerc
Normal file
3
.coveragerc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[run]
|
||||||
|
source = bookmarks
|
||||||
|
omit = bookmarks/tests/*
|
||||||
8
.github/workflows/main.yaml
vendored
8
.github/workflows/main.yaml
vendored
@@ -12,7 +12,13 @@ jobs:
|
|||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v1
|
||||||
with:
|
with:
|
||||||
python-version: 3.7
|
python-version: 3.7
|
||||||
- name: Install dependencies
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
- name: Install Python dependencies
|
||||||
run: pip install -r requirements.txt
|
run: pip install -r requirements.txt
|
||||||
|
- name: Install Node dependencies
|
||||||
|
run: npm install
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: python manage.py test
|
run: python manage.py test
|
||||||
|
|||||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.6.4 (13/05/2021)
|
||||||
|
- Update dependencies for security fixes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v1.6.3 (07/04/2021)
|
## v1.6.3 (07/04/2021)
|
||||||
- [**bug**] relative names use the wrong "today" after day change [#107](https://github.com/sissbruecker/linkding/issues/107)
|
- [**bug**] relative names use the wrong "today" after day change [#107](https://github.com/sissbruecker/linkding/issues/107)
|
||||||
|
|
||||||
|
|||||||
@@ -62,43 +62,17 @@ def _base_bookmarks_query(user: User, query_string: str) -> QuerySet:
|
|||||||
|
|
||||||
|
|
||||||
def query_bookmark_tags(user: User, query_string: str) -> QuerySet:
|
def query_bookmark_tags(user: User, query_string: str) -> QuerySet:
|
||||||
return _base_bookmark_tags_query(user, query_string) \
|
bookmarks_query = query_bookmarks(user, query_string)
|
||||||
.filter(bookmark__is_archived=False) \
|
|
||||||
.distinct()
|
query_set = Tag.objects.filter(bookmark__in=bookmarks_query)
|
||||||
|
|
||||||
|
return query_set.distinct()
|
||||||
|
|
||||||
|
|
||||||
def query_archived_bookmark_tags(user: User, query_string: str) -> QuerySet:
|
def query_archived_bookmark_tags(user: User, query_string: str) -> QuerySet:
|
||||||
return _base_bookmark_tags_query(user, query_string) \
|
bookmarks_query = query_archived_bookmarks(user, query_string)
|
||||||
.filter(bookmark__is_archived=True) \
|
|
||||||
.distinct()
|
|
||||||
|
|
||||||
|
query_set = Tag.objects.filter(bookmark__in=bookmarks_query)
|
||||||
def _base_bookmark_tags_query(user: User, query_string: str) -> QuerySet:
|
|
||||||
query_set = Tag.objects
|
|
||||||
|
|
||||||
# Filter for user
|
|
||||||
query_set = query_set.filter(owner=user)
|
|
||||||
|
|
||||||
# Only show tags which have bookmarks
|
|
||||||
query_set = query_set.filter(bookmark__isnull=False)
|
|
||||||
|
|
||||||
# Split query into search terms and tags
|
|
||||||
query = _parse_query_string(query_string)
|
|
||||||
|
|
||||||
# Filter for search terms and tags
|
|
||||||
for term in query['search_terms']:
|
|
||||||
query_set = query_set.filter(
|
|
||||||
Q(bookmark__title__contains=term)
|
|
||||||
| Q(bookmark__description__contains=term)
|
|
||||||
| Q(bookmark__website_title__contains=term)
|
|
||||||
| Q(bookmark__website_description__contains=term)
|
|
||||||
| Q(bookmark__url__contains=term)
|
|
||||||
)
|
|
||||||
|
|
||||||
for tag_name in query['tag_names']:
|
|
||||||
query_set = query_set.filter(
|
|
||||||
bookmark__tags__name__iexact=tag_name
|
|
||||||
)
|
|
||||||
|
|
||||||
return query_set.distinct()
|
return query_set.distinct()
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from dataclasses import dataclass
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from bookmarks.models import Bookmark, parse_tag_string
|
from bookmarks.models import Bookmark, parse_tag_string
|
||||||
from bookmarks.services.parser import parse, NetscapeBookmark
|
from bookmarks.services.parser import parse, NetscapeBookmark
|
||||||
@@ -45,7 +46,10 @@ def _import_bookmark_tag(netscape_bookmark: NetscapeBookmark, user: User):
|
|||||||
bookmark = _get_or_create_bookmark(netscape_bookmark.href, user)
|
bookmark = _get_or_create_bookmark(netscape_bookmark.href, user)
|
||||||
|
|
||||||
bookmark.url = netscape_bookmark.href
|
bookmark.url = netscape_bookmark.href
|
||||||
bookmark.date_added = datetime.utcfromtimestamp(int(netscape_bookmark.date_added)).astimezone()
|
if netscape_bookmark.date_added:
|
||||||
|
bookmark.date_added = datetime.utcfromtimestamp(int(netscape_bookmark.date_added)).astimezone()
|
||||||
|
else:
|
||||||
|
bookmark.date_added = timezone.now()
|
||||||
bookmark.date_modified = bookmark.date_added
|
bookmark.date_modified = bookmark.date_added
|
||||||
bookmark.unread = False
|
bookmark.unread = False
|
||||||
bookmark.title = netscape_bookmark.title
|
bookmark.title = netscape_bookmark.title
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
import pyparsing as pp
|
import pyparsing as pp
|
||||||
|
|
||||||
@@ -9,7 +8,7 @@ class NetscapeBookmark:
|
|||||||
href: str
|
href: str
|
||||||
title: str
|
title: str
|
||||||
description: str
|
description: str
|
||||||
date_added: int
|
date_added: str
|
||||||
tag_string: str
|
tag_string: str
|
||||||
|
|
||||||
|
|
||||||
@@ -17,8 +16,7 @@ def extract_bookmark_link(tag):
|
|||||||
href = tag[0].href
|
href = tag[0].href
|
||||||
title = tag[0].text
|
title = tag[0].text
|
||||||
tag_string = tag[0].tags
|
tag_string = tag[0].tags
|
||||||
date_added_string = tag[0].add_date if tag[0].add_date else datetime.now().timestamp()
|
date_added = tag[0].add_date
|
||||||
date_added = int(date_added_string)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'href': href,
|
'href': href,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<ul class="bookmark-list">
|
<ul class="bookmark-list">
|
||||||
{% for bookmark in bookmarks %}
|
{% for bookmark in bookmarks %}
|
||||||
<li>
|
<li data-is-bookmark-item>
|
||||||
<label class="form-checkbox bulk-edit-toggle">
|
<label class="form-checkbox bulk-edit-toggle">
|
||||||
<input type="checkbox" name="bookmark_id" value="{{ bookmark.id }}">
|
<input type="checkbox" name="bookmark_id" value="{{ bookmark.id }}">
|
||||||
<i class="form-icon"></i>
|
<i class="form-icon"></i>
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
{# Highlight first char of first tag in group #}
|
{# Highlight first char of first tag in group #}
|
||||||
{% if forloop.counter == 1 %}
|
{% if forloop.counter == 1 %}
|
||||||
<a href="?{% append_query_param q=tag.name|hash_tag %}"
|
<a href="?{% append_query_param q=tag.name|hash_tag %}"
|
||||||
class="mr-2">
|
class="mr-2" data-is-tag-item>
|
||||||
<span class="highlight-char">{{ tag.name|first_char }}</span><span>{{ tag.name|remaining_chars:1 }}</span>
|
<span class="highlight-char">{{ tag.name|first_char }}</span><span>{{ tag.name|remaining_chars:1 }}</span>
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
{# Render remaining tags normally #}
|
{# Render remaining tags normally #}
|
||||||
<a href="?{% append_query_param q=tag.name|hash_tag %}"
|
<a href="?{% append_query_param q=tag.name|hash_tag %}"
|
||||||
class="mr-2">
|
class="mr-2" data-is-tag-item>
|
||||||
<span>{{ tag.name }}</span>
|
<span>{{ tag.name }}</span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import random
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
@@ -16,12 +18,29 @@ class BookmarkFactoryMixin:
|
|||||||
|
|
||||||
return self.user
|
return self.user
|
||||||
|
|
||||||
def setup_bookmark(self, is_archived: bool = False, tags: [Tag] = [], user: User = None):
|
def setup_bookmark(self,
|
||||||
|
is_archived: bool = False,
|
||||||
|
tags=None,
|
||||||
|
user: User = None,
|
||||||
|
url: str = '',
|
||||||
|
title: str = '',
|
||||||
|
description: str = '',
|
||||||
|
website_title: str = '',
|
||||||
|
website_description: str = '',
|
||||||
|
):
|
||||||
|
if tags is None:
|
||||||
|
tags = []
|
||||||
if user is None:
|
if user is None:
|
||||||
user = self.get_or_create_test_user()
|
user = self.get_or_create_test_user()
|
||||||
unique_id = get_random_string(length=32)
|
if not url:
|
||||||
|
unique_id = get_random_string(length=32)
|
||||||
|
url = 'https://example.com/' + unique_id
|
||||||
bookmark = Bookmark(
|
bookmark = Bookmark(
|
||||||
url='https://example.com/' + unique_id,
|
url=url,
|
||||||
|
title=title,
|
||||||
|
description=description,
|
||||||
|
website_title=website_title,
|
||||||
|
website_description=website_description,
|
||||||
date_added=timezone.now(),
|
date_added=timezone.now(),
|
||||||
date_modified=timezone.now(),
|
date_modified=timezone.now(),
|
||||||
owner=user,
|
owner=user,
|
||||||
@@ -33,10 +52,11 @@ class BookmarkFactoryMixin:
|
|||||||
bookmark.save()
|
bookmark.save()
|
||||||
return bookmark
|
return bookmark
|
||||||
|
|
||||||
def setup_tag(self, user: User = None):
|
def setup_tag(self, user: User = None, name: str = ''):
|
||||||
if user is None:
|
if user is None:
|
||||||
user = self.get_or_create_test_user()
|
user = self.get_or_create_test_user()
|
||||||
name = get_random_string(length=32)
|
if not name:
|
||||||
|
name = get_random_string(length=32)
|
||||||
tag = Tag(name=name, date_added=timezone.now(), owner=user)
|
tag = Tag(name=name, date_added=timezone.now(), owner=user)
|
||||||
tag.save()
|
tag.save()
|
||||||
return tag
|
return tag
|
||||||
@@ -62,3 +82,38 @@ class LinkdingApiTestCase(APITestCase):
|
|||||||
response = self.client.delete(url)
|
response = self.client.delete(url)
|
||||||
self.assertEqual(response.status_code, expected_status_code)
|
self.assertEqual(response.status_code, expected_status_code)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
_words = [
|
||||||
|
'quasi',
|
||||||
|
'consequatur',
|
||||||
|
'necessitatibus',
|
||||||
|
'debitis',
|
||||||
|
'quod',
|
||||||
|
'vero',
|
||||||
|
'qui',
|
||||||
|
'commodi',
|
||||||
|
'quod',
|
||||||
|
'odio',
|
||||||
|
'aliquam',
|
||||||
|
'veniam',
|
||||||
|
'architecto',
|
||||||
|
'consequatur',
|
||||||
|
'autem',
|
||||||
|
'qui',
|
||||||
|
'iste',
|
||||||
|
'asperiores',
|
||||||
|
'soluta',
|
||||||
|
'et',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def random_sentence(num_words: int = None, including_word: str = ''):
|
||||||
|
if num_words is None:
|
||||||
|
num_words = random.randint(5, 10)
|
||||||
|
selected_words = random.choices(_words, k=num_words)
|
||||||
|
if including_word:
|
||||||
|
selected_words.append(including_word)
|
||||||
|
random.shuffle(selected_words)
|
||||||
|
|
||||||
|
return ' '.join(selected_words)
|
||||||
|
|||||||
BIN
bookmarks/tests/resources/invalid_import_file.png
Normal file
BIN
bookmarks/tests/resources/invalid_import_file.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
20
bookmarks/tests/resources/simple_valid_import_file.html
Normal file
20
bookmarks/tests/resources/simple_valid_import_file.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE NETSCAPE-Bookmark-file-1>
|
||||||
|
|
||||||
|
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
|
||||||
|
|
||||||
|
<TITLE>Bookmarks</TITLE>
|
||||||
|
|
||||||
|
<H1>Bookmarks</H1>
|
||||||
|
|
||||||
|
<DL><p>
|
||||||
|
|
||||||
|
<DT><A HREF="https://example.com/1" ADD_DATE="1616337559" PRIVATE="0" TOREAD="0" TAGS="tag1">test title 1</A>
|
||||||
|
<DD>test description 1
|
||||||
|
|
||||||
|
<DT><A HREF="https://example.com/2" ADD_DATE="1616337559" PRIVATE="0" TOREAD="0" TAGS="tag2">test title 2</A>
|
||||||
|
<DD>test description 2
|
||||||
|
|
||||||
|
<DT><A HREF="https://example.com/3" ADD_DATE="1616337559" PRIVATE="0" TOREAD="0" TAGS="tag3">test title 3</A>
|
||||||
|
<DD>test description 3
|
||||||
|
|
||||||
|
</DL><p>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE NETSCAPE-Bookmark-file-1>
|
||||||
|
|
||||||
|
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
|
||||||
|
|
||||||
|
<TITLE>Bookmarks</TITLE>
|
||||||
|
|
||||||
|
<H1>Bookmarks</H1>
|
||||||
|
|
||||||
|
<DL><p>
|
||||||
|
|
||||||
|
<DT><A HREF="https://example.com/1" ADD_DATE="invaliddate" PRIVATE="0" TOREAD="0" TAGS="tag1">test title 1</A>
|
||||||
|
<DD>test description 1
|
||||||
|
|
||||||
|
<DT><A HREF="https://example.com/2" ADD_DATE="1616337559" PRIVATE="0" TOREAD="0" TAGS="tag2">test title 2</A>
|
||||||
|
<DD>test description 2
|
||||||
|
|
||||||
|
<DT><A HREF="https://example.com/3" ADD_DATE="1616337559" PRIVATE="0" TOREAD="0" TAGS="tag3">test title 3</A>
|
||||||
|
<DD>test description 3
|
||||||
|
|
||||||
|
</DL><p>
|
||||||
35
bookmarks/tests/test_bookmark_archive_view.py
Normal file
35
bookmarks/tests/test_bookmark_archive_view.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
|
|
||||||
|
class BookmarkArchiveViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
user = self.get_or_create_test_user()
|
||||||
|
self.client.force_login(user)
|
||||||
|
|
||||||
|
def test_should_archive_bookmark(self):
|
||||||
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
|
self.client.get(reverse('bookmarks:archive', args=[bookmark.id]))
|
||||||
|
bookmark.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertTrue(bookmark.is_archived)
|
||||||
|
|
||||||
|
def test_should_redirect_to_index(self):
|
||||||
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
|
response = self.client.get(reverse('bookmarks:archive', args=[bookmark.id]))
|
||||||
|
|
||||||
|
self.assertRedirects(response, reverse('bookmarks:index'))
|
||||||
|
|
||||||
|
def test_should_redirect_to_return_url_when_specified(self):
|
||||||
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
reverse('bookmarks:archive', args=[bookmark.id]) + '?return_url=' + reverse('bookmarks:close')
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRedirects(response, reverse('bookmarks:close'))
|
||||||
132
bookmarks/tests/test_bookmark_archived_view.py
Normal file
132
bookmarks/tests/test_bookmark_archived_view.py
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from bookmarks.models import Bookmark, Tag
|
||||||
|
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
|
|
||||||
|
class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
user = self.get_or_create_test_user()
|
||||||
|
self.client.force_login(user)
|
||||||
|
|
||||||
|
def assertVisibleBookmarks(self, response, bookmarks: [Bookmark]):
|
||||||
|
html = response.content.decode()
|
||||||
|
self.assertContains(response, 'data-is-bookmark-item', count=len(bookmarks))
|
||||||
|
|
||||||
|
for bookmark in bookmarks:
|
||||||
|
self.assertInHTML(
|
||||||
|
'<a href="{0}" target="_blank" rel="noopener">{1}</a>'.format(bookmark.url, bookmark.resolved_title),
|
||||||
|
html
|
||||||
|
)
|
||||||
|
|
||||||
|
def assertInvisibleBookmarks(self, response, bookmarks: [Bookmark]):
|
||||||
|
html = response.content.decode()
|
||||||
|
|
||||||
|
for bookmark in bookmarks:
|
||||||
|
self.assertInHTML(
|
||||||
|
'<a href="{0}" target="_blank" rel="noopener">{1}</a>'.format(bookmark.url, bookmark.resolved_title),
|
||||||
|
html,
|
||||||
|
count=0
|
||||||
|
)
|
||||||
|
|
||||||
|
def assertVisibleTags(self, response, tags: [Tag]):
|
||||||
|
self.assertContains(response, 'data-is-tag-item', count=len(tags))
|
||||||
|
|
||||||
|
for tag in tags:
|
||||||
|
self.assertContains(response, tag.name)
|
||||||
|
|
||||||
|
def assertInvisibleTags(self, response, tags: [Tag]):
|
||||||
|
for tag in tags:
|
||||||
|
self.assertNotContains(response, tag.name)
|
||||||
|
|
||||||
|
def test_should_list_archived_and_user_owned_bookmarks(self):
|
||||||
|
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
|
||||||
|
visible_bookmarks = [
|
||||||
|
self.setup_bookmark(is_archived=True),
|
||||||
|
self.setup_bookmark(is_archived=True),
|
||||||
|
self.setup_bookmark(is_archived=True)
|
||||||
|
]
|
||||||
|
invisible_bookmarks = [
|
||||||
|
self.setup_bookmark(is_archived=False),
|
||||||
|
self.setup_bookmark(is_archived=True, user=other_user),
|
||||||
|
]
|
||||||
|
|
||||||
|
response = self.client.get(reverse('bookmarks:archived'))
|
||||||
|
|
||||||
|
self.assertContains(response, '<ul class="bookmark-list">') # Should render list
|
||||||
|
self.assertVisibleBookmarks(response, visible_bookmarks)
|
||||||
|
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
||||||
|
|
||||||
|
def test_should_list_bookmarks_matching_query(self):
|
||||||
|
visible_bookmarks = [
|
||||||
|
self.setup_bookmark(is_archived=True, title='searchvalue'),
|
||||||
|
self.setup_bookmark(is_archived=True, title='searchvalue'),
|
||||||
|
self.setup_bookmark(is_archived=True, title='searchvalue')
|
||||||
|
]
|
||||||
|
invisible_bookmarks = [
|
||||||
|
self.setup_bookmark(is_archived=True),
|
||||||
|
self.setup_bookmark(is_archived=True),
|
||||||
|
self.setup_bookmark(is_archived=True)
|
||||||
|
]
|
||||||
|
|
||||||
|
response = self.client.get(reverse('bookmarks:archived') + '?q=searchvalue')
|
||||||
|
|
||||||
|
self.assertContains(response, '<ul class="bookmark-list">') # Should render list
|
||||||
|
self.assertVisibleBookmarks(response, visible_bookmarks)
|
||||||
|
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
||||||
|
|
||||||
|
def test_should_list_tags_for_archived_and_user_owned_bookmarks(self):
|
||||||
|
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
|
||||||
|
visible_tags = [
|
||||||
|
self.setup_tag(),
|
||||||
|
self.setup_tag(),
|
||||||
|
self.setup_tag(),
|
||||||
|
]
|
||||||
|
invisible_tags = [
|
||||||
|
self.setup_tag(), # unused tag
|
||||||
|
self.setup_tag(), # used in archived bookmark
|
||||||
|
self.setup_tag(user=other_user), # belongs to other user
|
||||||
|
]
|
||||||
|
|
||||||
|
# Assign tags to some bookmarks with duplicates
|
||||||
|
self.setup_bookmark(is_archived=True, tags=[visible_tags[0]])
|
||||||
|
self.setup_bookmark(is_archived=True, tags=[visible_tags[0]])
|
||||||
|
self.setup_bookmark(is_archived=True, tags=[visible_tags[1]])
|
||||||
|
self.setup_bookmark(is_archived=True, tags=[visible_tags[1]])
|
||||||
|
self.setup_bookmark(is_archived=True, tags=[visible_tags[2]])
|
||||||
|
self.setup_bookmark(is_archived=True, tags=[visible_tags[2]])
|
||||||
|
|
||||||
|
self.setup_bookmark(is_archived=False, tags=[invisible_tags[1]])
|
||||||
|
self.setup_bookmark(is_archived=True, tags=[invisible_tags[2]], user=other_user)
|
||||||
|
|
||||||
|
response = self.client.get(reverse('bookmarks:archived'))
|
||||||
|
|
||||||
|
self.assertVisibleTags(response, visible_tags)
|
||||||
|
self.assertInvisibleTags(response, invisible_tags)
|
||||||
|
|
||||||
|
def test_should_list_tags_for_bookmarks_matching_query(self):
|
||||||
|
visible_tags = [
|
||||||
|
self.setup_tag(),
|
||||||
|
self.setup_tag(),
|
||||||
|
self.setup_tag(),
|
||||||
|
]
|
||||||
|
invisible_tags = [
|
||||||
|
self.setup_tag(),
|
||||||
|
self.setup_tag(),
|
||||||
|
self.setup_tag(),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.setup_bookmark(is_archived=True, tags=[visible_tags[0]], title='searchvalue'),
|
||||||
|
self.setup_bookmark(is_archived=True, tags=[visible_tags[1]], title='searchvalue')
|
||||||
|
self.setup_bookmark(is_archived=True, tags=[visible_tags[2]], title='searchvalue')
|
||||||
|
self.setup_bookmark(is_archived=True, tags=[invisible_tags[0]])
|
||||||
|
self.setup_bookmark(is_archived=True, tags=[invisible_tags[1]])
|
||||||
|
self.setup_bookmark(is_archived=True, tags=[invisible_tags[2]])
|
||||||
|
|
||||||
|
response = self.client.get(reverse('bookmarks:archived') + '?q=searchvalue')
|
||||||
|
|
||||||
|
self.assertVisibleTags(response, visible_tags)
|
||||||
|
self.assertInvisibleTags(response, invisible_tags)
|
||||||
@@ -6,7 +6,7 @@ from bookmarks.models import Bookmark
|
|||||||
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
|
|
||||||
class BulkEditIntegrationTests(TestCase, BookmarkFactoryMixin):
|
class BookmarkBulkEditViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
user = self.get_or_create_test_user()
|
user = self.get_or_create_test_user()
|
||||||
97
bookmarks/tests/test_bookmark_edit_view.py
Normal file
97
bookmarks/tests/test_bookmark_edit_view.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from bookmarks.models import build_tag_string
|
||||||
|
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
|
|
||||||
|
class BookmarkEditViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
user = self.get_or_create_test_user()
|
||||||
|
self.client.force_login(user)
|
||||||
|
|
||||||
|
def create_form_data(self, overrides=None):
|
||||||
|
if overrides is None:
|
||||||
|
overrides = {}
|
||||||
|
form_data = {
|
||||||
|
'url': 'http://example.com/edited',
|
||||||
|
'tag_string': 'editedtag1 editedtag2',
|
||||||
|
'title': 'edited title',
|
||||||
|
'description': 'edited description',
|
||||||
|
'return_url': reverse('bookmarks:index'),
|
||||||
|
}
|
||||||
|
return {**form_data, **overrides}
|
||||||
|
|
||||||
|
def test_should_edit_bookmark(self):
|
||||||
|
bookmark = self.setup_bookmark()
|
||||||
|
form_data = self.create_form_data({'id': bookmark.id})
|
||||||
|
|
||||||
|
self.client.post(reverse('bookmarks:edit', args=[bookmark.id]), form_data)
|
||||||
|
|
||||||
|
bookmark.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(bookmark.owner, self.user)
|
||||||
|
self.assertEqual(bookmark.url, form_data['url'])
|
||||||
|
self.assertEqual(bookmark.title, form_data['title'])
|
||||||
|
self.assertEqual(bookmark.description, form_data['description'])
|
||||||
|
self.assertEqual(bookmark.tags.count(), 2)
|
||||||
|
self.assertEqual(bookmark.tags.all()[0].name, 'editedtag1')
|
||||||
|
self.assertEqual(bookmark.tags.all()[1].name, 'editedtag2')
|
||||||
|
|
||||||
|
def test_should_use_bookmark_index_as_default_return_url(self):
|
||||||
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
|
response = self.client.get(reverse('bookmarks:edit', args=[bookmark.id]))
|
||||||
|
html = response.content.decode()
|
||||||
|
|
||||||
|
self.assertInHTML(
|
||||||
|
'<input type="hidden" name="return_url" value="{0}" '
|
||||||
|
'id="id_return_url">'.format(reverse('bookmarks:index')),
|
||||||
|
html)
|
||||||
|
|
||||||
|
def test_should_prefill_bookmark_form_fields(self):
|
||||||
|
tag1 = self.setup_tag()
|
||||||
|
tag2 = self.setup_tag()
|
||||||
|
bookmark = self.setup_bookmark(tags=[tag1, tag2], title='edited title', description='edited description')
|
||||||
|
|
||||||
|
response = self.client.get(reverse('bookmarks:edit', args=[bookmark.id]))
|
||||||
|
html = response.content.decode()
|
||||||
|
|
||||||
|
self.assertInHTML('<input type="text" name="url" '
|
||||||
|
'value="{0}" placeholder=" " '
|
||||||
|
'autofocus class="form-input" required '
|
||||||
|
'id="id_url">'.format(bookmark.url),
|
||||||
|
html)
|
||||||
|
|
||||||
|
tag_string = build_tag_string(bookmark.tag_names, ' ')
|
||||||
|
self.assertInHTML('<input type="text" name="tag_string" '
|
||||||
|
'value="{0}" autocomplete="off" '
|
||||||
|
'class="form-input" '
|
||||||
|
'id="id_tag_string">'.format(tag_string),
|
||||||
|
html)
|
||||||
|
|
||||||
|
self.assertInHTML('<input type="text" name="title" maxlength="512" '
|
||||||
|
'autocomplete="off" class="form-input" '
|
||||||
|
'value="{0}" id="id_title">'.format(bookmark.title),
|
||||||
|
html)
|
||||||
|
|
||||||
|
self.assertInHTML('<textarea name="description" cols="40" rows="4" class="form-input" id="id_description">{0}'
|
||||||
|
'</textarea>'.format(bookmark.description),
|
||||||
|
html)
|
||||||
|
|
||||||
|
def test_should_prefill_return_url_from_url_parameter(self):
|
||||||
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
|
response = self.client.get(reverse('bookmarks:edit', args=[bookmark.id]) + '?return_url=/test-return-url')
|
||||||
|
html = response.content.decode()
|
||||||
|
|
||||||
|
self.assertInHTML('<input type="hidden" name="return_url" value="/test-return-url" id="id_return_url">', html)
|
||||||
|
|
||||||
|
def test_should_redirect_to_return_url(self):
|
||||||
|
bookmark = self.setup_bookmark()
|
||||||
|
form_data = self.create_form_data({'return_url': reverse('bookmarks:close')})
|
||||||
|
|
||||||
|
response = self.client.post(reverse('bookmarks:edit', args=[bookmark.id]), form_data)
|
||||||
|
|
||||||
|
self.assertRedirects(response, form_data['return_url'])
|
||||||
132
bookmarks/tests/test_bookmark_index_view.py
Normal file
132
bookmarks/tests/test_bookmark_index_view.py
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from bookmarks.models import Bookmark, Tag
|
||||||
|
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
|
|
||||||
|
class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
user = self.get_or_create_test_user()
|
||||||
|
self.client.force_login(user)
|
||||||
|
|
||||||
|
def assertVisibleBookmarks(self, response, bookmarks: [Bookmark]):
|
||||||
|
html = response.content.decode()
|
||||||
|
self.assertContains(response, 'data-is-bookmark-item', count=len(bookmarks))
|
||||||
|
|
||||||
|
for bookmark in bookmarks:
|
||||||
|
self.assertInHTML(
|
||||||
|
'<a href="{0}" target="_blank" rel="noopener">{1}</a>'.format(bookmark.url, bookmark.resolved_title),
|
||||||
|
html
|
||||||
|
)
|
||||||
|
|
||||||
|
def assertInvisibleBookmarks(self, response, bookmarks: [Bookmark]):
|
||||||
|
html = response.content.decode()
|
||||||
|
|
||||||
|
for bookmark in bookmarks:
|
||||||
|
self.assertInHTML(
|
||||||
|
'<a href="{0}" target="_blank" rel="noopener">{1}</a>'.format(bookmark.url, bookmark.resolved_title),
|
||||||
|
html,
|
||||||
|
count=0
|
||||||
|
)
|
||||||
|
|
||||||
|
def assertVisibleTags(self, response, tags: [Tag]):
|
||||||
|
self.assertContains(response, 'data-is-tag-item', count=len(tags))
|
||||||
|
|
||||||
|
for tag in tags:
|
||||||
|
self.assertContains(response, tag.name)
|
||||||
|
|
||||||
|
def assertInvisibleTags(self, response, tags: [Tag]):
|
||||||
|
for tag in tags:
|
||||||
|
self.assertNotContains(response, tag.name)
|
||||||
|
|
||||||
|
def test_should_list_unarchived_and_user_owned_bookmarks(self):
|
||||||
|
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
|
||||||
|
visible_bookmarks = [
|
||||||
|
self.setup_bookmark(),
|
||||||
|
self.setup_bookmark(),
|
||||||
|
self.setup_bookmark()
|
||||||
|
]
|
||||||
|
invisible_bookmarks = [
|
||||||
|
self.setup_bookmark(is_archived=True),
|
||||||
|
self.setup_bookmark(user=other_user),
|
||||||
|
]
|
||||||
|
|
||||||
|
response = self.client.get(reverse('bookmarks:index'))
|
||||||
|
|
||||||
|
self.assertContains(response, '<ul class="bookmark-list">') # Should render list
|
||||||
|
self.assertVisibleBookmarks(response, visible_bookmarks)
|
||||||
|
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
||||||
|
|
||||||
|
def test_should_list_bookmarks_matching_query(self):
|
||||||
|
visible_bookmarks = [
|
||||||
|
self.setup_bookmark(title='searchvalue'),
|
||||||
|
self.setup_bookmark(title='searchvalue'),
|
||||||
|
self.setup_bookmark(title='searchvalue')
|
||||||
|
]
|
||||||
|
invisible_bookmarks = [
|
||||||
|
self.setup_bookmark(),
|
||||||
|
self.setup_bookmark(),
|
||||||
|
self.setup_bookmark()
|
||||||
|
]
|
||||||
|
|
||||||
|
response = self.client.get(reverse('bookmarks:index') + '?q=searchvalue')
|
||||||
|
|
||||||
|
self.assertContains(response, '<ul class="bookmark-list">') # Should render list
|
||||||
|
self.assertVisibleBookmarks(response, visible_bookmarks)
|
||||||
|
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
||||||
|
|
||||||
|
def test_should_list_tags_for_unarchived_and_user_owned_bookmarks(self):
|
||||||
|
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
|
||||||
|
visible_tags = [
|
||||||
|
self.setup_tag(),
|
||||||
|
self.setup_tag(),
|
||||||
|
self.setup_tag(),
|
||||||
|
]
|
||||||
|
invisible_tags = [
|
||||||
|
self.setup_tag(), # unused tag
|
||||||
|
self.setup_tag(), # used in archived bookmark
|
||||||
|
self.setup_tag(user=other_user), # belongs to other user
|
||||||
|
]
|
||||||
|
|
||||||
|
# Assign tags to some bookmarks with duplicates
|
||||||
|
self.setup_bookmark(tags=[visible_tags[0]])
|
||||||
|
self.setup_bookmark(tags=[visible_tags[0]])
|
||||||
|
self.setup_bookmark(tags=[visible_tags[1]])
|
||||||
|
self.setup_bookmark(tags=[visible_tags[1]])
|
||||||
|
self.setup_bookmark(tags=[visible_tags[2]])
|
||||||
|
self.setup_bookmark(tags=[visible_tags[2]])
|
||||||
|
|
||||||
|
self.setup_bookmark(tags=[invisible_tags[1]], is_archived=True)
|
||||||
|
self.setup_bookmark(tags=[invisible_tags[2]], user=other_user)
|
||||||
|
|
||||||
|
response = self.client.get(reverse('bookmarks:index'))
|
||||||
|
|
||||||
|
self.assertVisibleTags(response, visible_tags)
|
||||||
|
self.assertInvisibleTags(response, invisible_tags)
|
||||||
|
|
||||||
|
def test_should_list_tags_for_bookmarks_matching_query(self):
|
||||||
|
visible_tags = [
|
||||||
|
self.setup_tag(),
|
||||||
|
self.setup_tag(),
|
||||||
|
self.setup_tag(),
|
||||||
|
]
|
||||||
|
invisible_tags = [
|
||||||
|
self.setup_tag(),
|
||||||
|
self.setup_tag(),
|
||||||
|
self.setup_tag(),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.setup_bookmark(tags=[visible_tags[0]], title='searchvalue'),
|
||||||
|
self.setup_bookmark(tags=[visible_tags[1]], title='searchvalue')
|
||||||
|
self.setup_bookmark(tags=[visible_tags[2]], title='searchvalue')
|
||||||
|
self.setup_bookmark(tags=[invisible_tags[0]])
|
||||||
|
self.setup_bookmark(tags=[invisible_tags[1]])
|
||||||
|
self.setup_bookmark(tags=[invisible_tags[2]])
|
||||||
|
|
||||||
|
response = self.client.get(reverse('bookmarks:index') + '?q=searchvalue')
|
||||||
|
|
||||||
|
self.assertVisibleTags(response, visible_tags)
|
||||||
|
self.assertInvisibleTags(response, invisible_tags)
|
||||||
81
bookmarks/tests/test_bookmark_new_view.py
Normal file
81
bookmarks/tests/test_bookmark_new_view.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from bookmarks.models import Bookmark
|
||||||
|
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
|
|
||||||
|
class BookmarkNewViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
user = self.get_or_create_test_user()
|
||||||
|
self.client.force_login(user)
|
||||||
|
|
||||||
|
def create_form_data(self, overrides=None):
|
||||||
|
if overrides is None:
|
||||||
|
overrides = {}
|
||||||
|
form_data = {
|
||||||
|
'url': 'http://example.com',
|
||||||
|
'tag_string': 'tag1 tag2',
|
||||||
|
'title': 'test title',
|
||||||
|
'description': 'test description',
|
||||||
|
'auto_close': '',
|
||||||
|
}
|
||||||
|
return {**form_data, **overrides}
|
||||||
|
|
||||||
|
def test_should_create_new_bookmark(self):
|
||||||
|
form_data = self.create_form_data()
|
||||||
|
|
||||||
|
self.client.post(reverse('bookmarks:new'), form_data)
|
||||||
|
|
||||||
|
self.assertEqual(Bookmark.objects.count(), 1)
|
||||||
|
|
||||||
|
bookmark = Bookmark.objects.first()
|
||||||
|
self.assertEqual(bookmark.owner, self.user)
|
||||||
|
self.assertEqual(bookmark.url, form_data['url'])
|
||||||
|
self.assertEqual(bookmark.title, form_data['title'])
|
||||||
|
self.assertEqual(bookmark.description, form_data['description'])
|
||||||
|
self.assertEqual(bookmark.tags.count(), 2)
|
||||||
|
self.assertEqual(bookmark.tags.all()[0].name, 'tag1')
|
||||||
|
self.assertEqual(bookmark.tags.all()[1].name, 'tag2')
|
||||||
|
|
||||||
|
def test_should_prefill_url_from_url_parameter(self):
|
||||||
|
response = self.client.get(reverse('bookmarks:new') + '?url=http://example.com')
|
||||||
|
html = response.content.decode()
|
||||||
|
|
||||||
|
self.assertInHTML(
|
||||||
|
'<input type="text" name="url" value="http://example.com" '
|
||||||
|
'placeholder=" " autofocus class="form-input" required '
|
||||||
|
'id="id_url">',
|
||||||
|
html)
|
||||||
|
|
||||||
|
def test_should_enable_auto_close_when_specified_in_url_parameter(self):
|
||||||
|
response = self.client.get(
|
||||||
|
reverse('bookmarks:new') + '?auto_close')
|
||||||
|
html = response.content.decode()
|
||||||
|
|
||||||
|
self.assertInHTML(
|
||||||
|
'<input type="hidden" name="auto_close" value="true" '
|
||||||
|
'id="id_auto_close">',
|
||||||
|
html)
|
||||||
|
|
||||||
|
def test_should_not_enable_auto_close_when_not_specified_in_url_parameter(
|
||||||
|
self):
|
||||||
|
response = self.client.get(reverse('bookmarks:new'))
|
||||||
|
html = response.content.decode()
|
||||||
|
|
||||||
|
self.assertInHTML('<input type="hidden" name="auto_close" id="id_auto_close">',html)
|
||||||
|
|
||||||
|
def test_should_redirect_to_index_view(self):
|
||||||
|
form_data = self.create_form_data()
|
||||||
|
|
||||||
|
response = self.client.post(reverse('bookmarks:new'), form_data)
|
||||||
|
|
||||||
|
self.assertRedirects(response, reverse('bookmarks:index'))
|
||||||
|
|
||||||
|
def test_auto_close_should_redirect_to_close_view(self):
|
||||||
|
form_data = self.create_form_data({'auto_close': 'true'})
|
||||||
|
|
||||||
|
response = self.client.post(reverse('bookmarks:new'), form_data)
|
||||||
|
|
||||||
|
self.assertRedirects(response, reverse('bookmarks:close'))
|
||||||
35
bookmarks/tests/test_bookmark_remove_view.py
Normal file
35
bookmarks/tests/test_bookmark_remove_view.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from bookmarks.models import Bookmark
|
||||||
|
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
|
|
||||||
|
class BookmarkRemoveViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
user = self.get_or_create_test_user()
|
||||||
|
self.client.force_login(user)
|
||||||
|
|
||||||
|
def test_should_delete_bookmark(self):
|
||||||
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
|
self.client.get(reverse('bookmarks:remove', args=[bookmark.id]))
|
||||||
|
|
||||||
|
self.assertEqual(Bookmark.objects.count(), 0)
|
||||||
|
|
||||||
|
def test_should_redirect_to_index(self):
|
||||||
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
|
response = self.client.get(reverse('bookmarks:remove', args=[bookmark.id]))
|
||||||
|
|
||||||
|
self.assertRedirects(response, reverse('bookmarks:index'))
|
||||||
|
|
||||||
|
def test_should_redirect_to_return_url_when_specified(self):
|
||||||
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
reverse('bookmarks:remove', args=[bookmark.id]) + '?return_url=' + reverse('bookmarks:close')
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRedirects(response, reverse('bookmarks:close'))
|
||||||
35
bookmarks/tests/test_bookmark_unarchive_view.py
Normal file
35
bookmarks/tests/test_bookmark_unarchive_view.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
|
|
||||||
|
class BookmarkUnarchiveViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
user = self.get_or_create_test_user()
|
||||||
|
self.client.force_login(user)
|
||||||
|
|
||||||
|
def test_should_unarchive_bookmark(self):
|
||||||
|
bookmark = self.setup_bookmark(is_archived=True)
|
||||||
|
|
||||||
|
self.client.get(reverse('bookmarks:unarchive', args=[bookmark.id]))
|
||||||
|
bookmark.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertFalse(bookmark.is_archived)
|
||||||
|
|
||||||
|
def test_should_redirect_to_archive(self):
|
||||||
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
|
response = self.client.get(reverse('bookmarks:unarchive', args=[bookmark.id]))
|
||||||
|
|
||||||
|
self.assertRedirects(response, reverse('bookmarks:archived'))
|
||||||
|
|
||||||
|
def test_should_redirect_to_return_url_when_specified(self):
|
||||||
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
reverse('bookmarks:unarchive', args=[bookmark.id]) + '?return_url=' + reverse('bookmarks:close')
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRedirects(response, reverse('bookmarks:close'))
|
||||||
@@ -4,8 +4,8 @@ from django.template import Template, RequestContext
|
|||||||
from django.test import TestCase, RequestFactory
|
from django.test import TestCase, RequestFactory
|
||||||
from django.utils import timezone, formats
|
from django.utils import timezone, formats
|
||||||
|
|
||||||
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
|
||||||
from bookmarks.models import UserProfile
|
from bookmarks.models import UserProfile
|
||||||
|
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
|
|
||||||
class BookmarkListTagTest(TestCase, BookmarkFactoryMixin):
|
class BookmarkListTagTest(TestCase, BookmarkFactoryMixin):
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.test import SimpleTestCase, RequestFactory
|
|
||||||
from django.template import Template, RequestContext
|
from django.template import Template, RequestContext
|
||||||
|
from django.test import SimpleTestCase, RequestFactory
|
||||||
|
|
||||||
|
|
||||||
class PaginationTagTest(SimpleTestCase):
|
class PaginationTagTest(SimpleTestCase):
|
||||||
|
|||||||
@@ -1,14 +1,223 @@
|
|||||||
|
import operator
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.db.models import QuerySet
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from bookmarks import queries
|
from bookmarks import queries
|
||||||
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
from bookmarks.models import Bookmark
|
||||||
|
from bookmarks.tests.helpers import BookmarkFactoryMixin, random_sentence
|
||||||
|
from bookmarks.utils import unique
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class QueriesTestCase(TestCase, BookmarkFactoryMixin):
|
class QueriesTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
|
def setup_bookmark_search_data(self) -> None:
|
||||||
|
tag1 = self.setup_tag(name='tag1')
|
||||||
|
tag2 = self.setup_tag(name='tag2')
|
||||||
|
self.setup_tag(name='unused_tag1')
|
||||||
|
|
||||||
|
self.other_bookmarks = [
|
||||||
|
self.setup_bookmark(),
|
||||||
|
self.setup_bookmark(),
|
||||||
|
self.setup_bookmark(),
|
||||||
|
]
|
||||||
|
self.term1_bookmarks = [
|
||||||
|
self.setup_bookmark(url='http://example.com/term1'),
|
||||||
|
self.setup_bookmark(title=random_sentence(including_word='term1')),
|
||||||
|
self.setup_bookmark(description=random_sentence(including_word='term1')),
|
||||||
|
self.setup_bookmark(website_title=random_sentence(including_word='term1')),
|
||||||
|
self.setup_bookmark(website_description=random_sentence(including_word='term1')),
|
||||||
|
]
|
||||||
|
self.term1_term2_bookmarks = [
|
||||||
|
self.setup_bookmark(url='http://example.com/term1/term2'),
|
||||||
|
self.setup_bookmark(title=random_sentence(including_word='term1'),
|
||||||
|
description=random_sentence(including_word='term2')),
|
||||||
|
self.setup_bookmark(description=random_sentence(including_word='term1'),
|
||||||
|
title=random_sentence(including_word='term2')),
|
||||||
|
self.setup_bookmark(website_title=random_sentence(including_word='term1'),
|
||||||
|
title=random_sentence(including_word='term2')),
|
||||||
|
self.setup_bookmark(website_description=random_sentence(including_word='term1'),
|
||||||
|
title=random_sentence(including_word='term2')),
|
||||||
|
]
|
||||||
|
self.tag1_bookmarks = [
|
||||||
|
self.setup_bookmark(tags=[tag1]),
|
||||||
|
self.setup_bookmark(title=random_sentence(), tags=[tag1]),
|
||||||
|
self.setup_bookmark(description=random_sentence(), tags=[tag1]),
|
||||||
|
self.setup_bookmark(website_title=random_sentence(), tags=[tag1]),
|
||||||
|
self.setup_bookmark(website_description=random_sentence(), tags=[tag1]),
|
||||||
|
]
|
||||||
|
self.term1_tag1_bookmarks = [
|
||||||
|
self.setup_bookmark(url='http://example.com/term1', tags=[tag1]),
|
||||||
|
self.setup_bookmark(title=random_sentence(including_word='term1'), tags=[tag1]),
|
||||||
|
self.setup_bookmark(description=random_sentence(including_word='term1'), tags=[tag1]),
|
||||||
|
self.setup_bookmark(website_title=random_sentence(including_word='term1'), tags=[tag1]),
|
||||||
|
self.setup_bookmark(website_description=random_sentence(including_word='term1'), tags=[tag1]),
|
||||||
|
]
|
||||||
|
self.tag2_bookmarks = [
|
||||||
|
self.setup_bookmark(tags=[tag2]),
|
||||||
|
]
|
||||||
|
self.tag1_tag2_bookmarks = [
|
||||||
|
self.setup_bookmark(tags=[tag1, tag2]),
|
||||||
|
]
|
||||||
|
|
||||||
|
def setup_tag_search_data(self):
|
||||||
|
tag1 = self.setup_tag(name='tag1')
|
||||||
|
tag2 = self.setup_tag(name='tag2')
|
||||||
|
self.setup_tag(name='unused_tag1')
|
||||||
|
|
||||||
|
self.other_bookmarks = [
|
||||||
|
self.setup_bookmark(tags=[self.setup_tag()]),
|
||||||
|
self.setup_bookmark(tags=[self.setup_tag()]),
|
||||||
|
self.setup_bookmark(tags=[self.setup_tag()]),
|
||||||
|
]
|
||||||
|
self.term1_bookmarks = [
|
||||||
|
self.setup_bookmark(url='http://example.com/term1', tags=[self.setup_tag()]),
|
||||||
|
self.setup_bookmark(title=random_sentence(including_word='term1'), tags=[self.setup_tag()]),
|
||||||
|
self.setup_bookmark(description=random_sentence(including_word='term1'), tags=[self.setup_tag()]),
|
||||||
|
self.setup_bookmark(website_title=random_sentence(including_word='term1'), tags=[self.setup_tag()]),
|
||||||
|
self.setup_bookmark(website_description=random_sentence(including_word='term1'), tags=[self.setup_tag()]),
|
||||||
|
]
|
||||||
|
self.term1_term2_bookmarks = [
|
||||||
|
self.setup_bookmark(url='http://example.com/term1/term2', tags=[self.setup_tag()]),
|
||||||
|
self.setup_bookmark(title=random_sentence(including_word='term1'),
|
||||||
|
description=random_sentence(including_word='term2'),
|
||||||
|
tags=[self.setup_tag()]),
|
||||||
|
self.setup_bookmark(description=random_sentence(including_word='term1'),
|
||||||
|
title=random_sentence(including_word='term2'),
|
||||||
|
tags=[self.setup_tag()]),
|
||||||
|
self.setup_bookmark(website_title=random_sentence(including_word='term1'),
|
||||||
|
title=random_sentence(including_word='term2'),
|
||||||
|
tags=[self.setup_tag()]),
|
||||||
|
self.setup_bookmark(website_description=random_sentence(including_word='term1'),
|
||||||
|
title=random_sentence(including_word='term2'),
|
||||||
|
tags=[self.setup_tag()]),
|
||||||
|
]
|
||||||
|
self.tag1_bookmarks = [
|
||||||
|
self.setup_bookmark(tags=[tag1, self.setup_tag()]),
|
||||||
|
self.setup_bookmark(title=random_sentence(), tags=[tag1, self.setup_tag()]),
|
||||||
|
self.setup_bookmark(description=random_sentence(), tags=[tag1, self.setup_tag()]),
|
||||||
|
self.setup_bookmark(website_title=random_sentence(), tags=[tag1, self.setup_tag()]),
|
||||||
|
self.setup_bookmark(website_description=random_sentence(), tags=[tag1, self.setup_tag()]),
|
||||||
|
]
|
||||||
|
self.term1_tag1_bookmarks = [
|
||||||
|
self.setup_bookmark(url='http://example.com/term1', tags=[tag1, self.setup_tag()]),
|
||||||
|
self.setup_bookmark(title=random_sentence(including_word='term1'), tags=[tag1, self.setup_tag()]),
|
||||||
|
self.setup_bookmark(description=random_sentence(including_word='term1'), tags=[tag1, self.setup_tag()]),
|
||||||
|
self.setup_bookmark(website_title=random_sentence(including_word='term1'), tags=[tag1, self.setup_tag()]),
|
||||||
|
self.setup_bookmark(website_description=random_sentence(including_word='term1'),
|
||||||
|
tags=[tag1, self.setup_tag()]),
|
||||||
|
]
|
||||||
|
self.tag2_bookmarks = [
|
||||||
|
self.setup_bookmark(tags=[tag2, self.setup_tag()]),
|
||||||
|
]
|
||||||
|
self.tag1_tag2_bookmarks = [
|
||||||
|
self.setup_bookmark(tags=[tag1, tag2, self.setup_tag()]),
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_tags_from_bookmarks(self, bookmarks: [Bookmark]):
|
||||||
|
all_tags = []
|
||||||
|
for bookmark in bookmarks:
|
||||||
|
all_tags = all_tags + list(bookmark.tags.all())
|
||||||
|
return all_tags
|
||||||
|
|
||||||
|
def assertQueryResult(self, query: QuerySet, item_lists: [[any]]):
|
||||||
|
expected_items = []
|
||||||
|
for item_list in item_lists:
|
||||||
|
expected_items = expected_items + item_list
|
||||||
|
|
||||||
|
expected_items = unique(expected_items, operator.attrgetter('id'))
|
||||||
|
|
||||||
|
self.assertCountEqual(list(query), expected_items)
|
||||||
|
|
||||||
|
def test_query_bookmarks_should_return_all_for_empty_query(self):
|
||||||
|
self.setup_bookmark_search_data()
|
||||||
|
|
||||||
|
query = queries.query_bookmarks(self.get_or_create_test_user(), '')
|
||||||
|
self.assertQueryResult(query, [
|
||||||
|
self.other_bookmarks,
|
||||||
|
self.term1_bookmarks,
|
||||||
|
self.term1_term2_bookmarks,
|
||||||
|
self.tag1_bookmarks,
|
||||||
|
self.term1_tag1_bookmarks,
|
||||||
|
self.tag2_bookmarks,
|
||||||
|
self.tag1_tag2_bookmarks
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_query_bookmarks_should_search_single_term(self):
|
||||||
|
self.setup_bookmark_search_data()
|
||||||
|
|
||||||
|
query = queries.query_bookmarks(self.get_or_create_test_user(), 'term1')
|
||||||
|
self.assertQueryResult(query, [
|
||||||
|
self.term1_bookmarks,
|
||||||
|
self.term1_term2_bookmarks,
|
||||||
|
self.term1_tag1_bookmarks
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_query_bookmarks_should_search_multiple_terms(self):
|
||||||
|
self.setup_bookmark_search_data()
|
||||||
|
|
||||||
|
query = queries.query_bookmarks(self.get_or_create_test_user(), 'term2 term1')
|
||||||
|
|
||||||
|
self.assertQueryResult(query, [self.term1_term2_bookmarks])
|
||||||
|
|
||||||
|
def test_query_bookmarks_should_search_single_tag(self):
|
||||||
|
self.setup_bookmark_search_data()
|
||||||
|
|
||||||
|
query = queries.query_bookmarks(self.get_or_create_test_user(), '#tag1')
|
||||||
|
|
||||||
|
self.assertQueryResult(query, [self.tag1_bookmarks, self.tag1_tag2_bookmarks, self.term1_tag1_bookmarks])
|
||||||
|
|
||||||
|
def test_query_bookmarks_should_search_multiple_tags(self):
|
||||||
|
self.setup_bookmark_search_data()
|
||||||
|
|
||||||
|
query = queries.query_bookmarks(self.get_or_create_test_user(), '#tag1 #tag2')
|
||||||
|
|
||||||
|
self.assertQueryResult(query, [self.tag1_tag2_bookmarks])
|
||||||
|
|
||||||
|
def test_query_bookmarks_should_search_multiple_tags_ignoring_casing(self):
|
||||||
|
self.setup_bookmark_search_data()
|
||||||
|
|
||||||
|
query = queries.query_bookmarks(self.get_or_create_test_user(), '#Tag1 #TAG2')
|
||||||
|
|
||||||
|
self.assertQueryResult(query, [self.tag1_tag2_bookmarks])
|
||||||
|
|
||||||
|
def test_query_bookmarks_should_search_terms_and_tags_combined(self):
|
||||||
|
self.setup_bookmark_search_data()
|
||||||
|
|
||||||
|
query = queries.query_bookmarks(self.get_or_create_test_user(), 'term1 #tag1')
|
||||||
|
|
||||||
|
self.assertQueryResult(query, [self.term1_tag1_bookmarks])
|
||||||
|
|
||||||
|
def test_query_bookmarks_should_return_no_matches(self):
|
||||||
|
self.setup_bookmark_search_data()
|
||||||
|
|
||||||
|
query = queries.query_bookmarks(self.get_or_create_test_user(), 'term3')
|
||||||
|
self.assertQueryResult(query, [])
|
||||||
|
|
||||||
|
query = queries.query_bookmarks(self.get_or_create_test_user(), 'term1 term3')
|
||||||
|
self.assertQueryResult(query, [])
|
||||||
|
|
||||||
|
query = queries.query_bookmarks(self.get_or_create_test_user(), 'term1 #tag2')
|
||||||
|
self.assertQueryResult(query, [])
|
||||||
|
|
||||||
|
query = queries.query_bookmarks(self.get_or_create_test_user(), '#tag3')
|
||||||
|
self.assertQueryResult(query, [])
|
||||||
|
|
||||||
|
# Unused tag
|
||||||
|
query = queries.query_bookmarks(self.get_or_create_test_user(), '#unused_tag1')
|
||||||
|
self.assertQueryResult(query, [])
|
||||||
|
|
||||||
|
# Unused tag combined with tag that is used
|
||||||
|
query = queries.query_bookmarks(self.get_or_create_test_user(), '#tag1 #unused_tag1')
|
||||||
|
self.assertQueryResult(query, [])
|
||||||
|
|
||||||
|
# Unused tag combined with term that is used
|
||||||
|
query = queries.query_bookmarks(self.get_or_create_test_user(), 'term1 #unused_tag1')
|
||||||
|
self.assertQueryResult(query, [])
|
||||||
|
|
||||||
def test_query_bookmarks_should_not_return_archived_bookmarks(self):
|
def test_query_bookmarks_should_not_return_archived_bookmarks(self):
|
||||||
bookmark1 = self.setup_bookmark()
|
bookmark1 = self.setup_bookmark()
|
||||||
bookmark2 = self.setup_bookmark()
|
bookmark2 = self.setup_bookmark()
|
||||||
@@ -18,7 +227,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
|
|||||||
|
|
||||||
query = queries.query_bookmarks(self.get_or_create_test_user(), '')
|
query = queries.query_bookmarks(self.get_or_create_test_user(), '')
|
||||||
|
|
||||||
self.assertCountEqual([bookmark1, bookmark2], list(query))
|
self.assertQueryResult(query, [[bookmark1, bookmark2]])
|
||||||
|
|
||||||
def test_query_archived_bookmarks_should_not_return_unarchived_bookmarks(self):
|
def test_query_archived_bookmarks_should_not_return_unarchived_bookmarks(self):
|
||||||
bookmark1 = self.setup_bookmark(is_archived=True)
|
bookmark1 = self.setup_bookmark(is_archived=True)
|
||||||
@@ -29,7 +238,156 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
|
|||||||
|
|
||||||
query = queries.query_archived_bookmarks(self.get_or_create_test_user(), '')
|
query = queries.query_archived_bookmarks(self.get_or_create_test_user(), '')
|
||||||
|
|
||||||
self.assertCountEqual([bookmark1, bookmark2], list(query))
|
self.assertQueryResult(query, [[bookmark1, bookmark2]])
|
||||||
|
|
||||||
|
def test_query_bookmarks_should_only_return_user_owned_bookmarks(self):
|
||||||
|
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
|
||||||
|
owned_bookmarks = [
|
||||||
|
self.setup_bookmark(),
|
||||||
|
self.setup_bookmark(),
|
||||||
|
self.setup_bookmark(),
|
||||||
|
]
|
||||||
|
self.setup_bookmark(user=other_user)
|
||||||
|
self.setup_bookmark(user=other_user)
|
||||||
|
self.setup_bookmark(user=other_user)
|
||||||
|
|
||||||
|
query = queries.query_bookmarks(self.user, '')
|
||||||
|
|
||||||
|
self.assertQueryResult(query, [owned_bookmarks])
|
||||||
|
|
||||||
|
def test_query_archived_bookmarks_should_only_return_user_owned_bookmarks(self):
|
||||||
|
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
|
||||||
|
owned_bookmarks = [
|
||||||
|
self.setup_bookmark(is_archived=True),
|
||||||
|
self.setup_bookmark(is_archived=True),
|
||||||
|
self.setup_bookmark(is_archived=True),
|
||||||
|
]
|
||||||
|
self.setup_bookmark(is_archived=True, user=other_user)
|
||||||
|
self.setup_bookmark(is_archived=True, user=other_user)
|
||||||
|
self.setup_bookmark(is_archived=True, user=other_user)
|
||||||
|
|
||||||
|
query = queries.query_archived_bookmarks(self.user, '')
|
||||||
|
|
||||||
|
self.assertQueryResult(query, [owned_bookmarks])
|
||||||
|
|
||||||
|
def test_query_bookmarks_should_use_tag_projection(self):
|
||||||
|
self.setup_bookmark_search_data()
|
||||||
|
|
||||||
|
# Test projection on bookmarks with tags
|
||||||
|
query = queries.query_bookmarks(self.user, '#tag1 #tag2')
|
||||||
|
|
||||||
|
for bookmark in query:
|
||||||
|
self.assertEqual(bookmark.tag_count, 2)
|
||||||
|
self.assertEqual(bookmark.tag_string, 'tag1,tag2')
|
||||||
|
self.assertTrue(bookmark.tag_projection)
|
||||||
|
|
||||||
|
# Test projection on bookmarks without tags
|
||||||
|
query = queries.query_bookmarks(self.user, 'term2')
|
||||||
|
|
||||||
|
for bookmark in query:
|
||||||
|
self.assertEqual(bookmark.tag_count, 0)
|
||||||
|
self.assertEqual(bookmark.tag_string, None)
|
||||||
|
self.assertTrue(bookmark.tag_projection)
|
||||||
|
|
||||||
|
def test_query_bookmark_tags_should_return_all_tags_for_empty_query(self):
|
||||||
|
self.setup_tag_search_data()
|
||||||
|
|
||||||
|
query = queries.query_bookmark_tags(self.user, '')
|
||||||
|
|
||||||
|
self.assertQueryResult(query, [
|
||||||
|
self.get_tags_from_bookmarks(self.other_bookmarks),
|
||||||
|
self.get_tags_from_bookmarks(self.term1_bookmarks),
|
||||||
|
self.get_tags_from_bookmarks(self.term1_term2_bookmarks),
|
||||||
|
self.get_tags_from_bookmarks(self.tag1_bookmarks),
|
||||||
|
self.get_tags_from_bookmarks(self.term1_tag1_bookmarks),
|
||||||
|
self.get_tags_from_bookmarks(self.tag2_bookmarks),
|
||||||
|
self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_query_bookmark_tags_should_search_single_term(self):
|
||||||
|
self.setup_tag_search_data()
|
||||||
|
|
||||||
|
query = queries.query_bookmark_tags(self.user, 'term1')
|
||||||
|
|
||||||
|
self.assertQueryResult(query, [
|
||||||
|
self.get_tags_from_bookmarks(self.term1_bookmarks),
|
||||||
|
self.get_tags_from_bookmarks(self.term1_term2_bookmarks),
|
||||||
|
self.get_tags_from_bookmarks(self.term1_tag1_bookmarks),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_query_bookmark_tags_should_search_multiple_terms(self):
|
||||||
|
self.setup_tag_search_data()
|
||||||
|
|
||||||
|
query = queries.query_bookmark_tags(self.user, 'term2 term1')
|
||||||
|
|
||||||
|
self.assertQueryResult(query, [
|
||||||
|
self.get_tags_from_bookmarks(self.term1_term2_bookmarks),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_query_bookmark_tags_should_search_single_tag(self):
|
||||||
|
self.setup_tag_search_data()
|
||||||
|
|
||||||
|
query = queries.query_bookmark_tags(self.user, '#tag1')
|
||||||
|
|
||||||
|
self.assertQueryResult(query, [
|
||||||
|
self.get_tags_from_bookmarks(self.tag1_bookmarks),
|
||||||
|
self.get_tags_from_bookmarks(self.term1_tag1_bookmarks),
|
||||||
|
self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_query_bookmark_tags_should_search_multiple_tags(self):
|
||||||
|
self.setup_tag_search_data()
|
||||||
|
|
||||||
|
query = queries.query_bookmark_tags(self.user, '#tag1 #tag2')
|
||||||
|
|
||||||
|
self.assertQueryResult(query, [
|
||||||
|
self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_query_bookmark_tags_should_search_multiple_tags_ignoring_casing(self):
|
||||||
|
self.setup_tag_search_data()
|
||||||
|
|
||||||
|
query = queries.query_bookmark_tags(self.user, '#Tag1 #TAG2')
|
||||||
|
|
||||||
|
self.assertQueryResult(query, [
|
||||||
|
self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_query_bookmark_tags_should_search_term_and_tag_combined(self):
|
||||||
|
self.setup_tag_search_data()
|
||||||
|
|
||||||
|
query = queries.query_bookmark_tags(self.user, 'term1 #tag1')
|
||||||
|
|
||||||
|
self.assertQueryResult(query, [
|
||||||
|
self.get_tags_from_bookmarks(self.term1_tag1_bookmarks),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_query_bookmark_tags_should_return_no_matches(self):
|
||||||
|
self.setup_tag_search_data()
|
||||||
|
|
||||||
|
query = queries.query_bookmark_tags(self.get_or_create_test_user(), 'term3')
|
||||||
|
self.assertQueryResult(query, [])
|
||||||
|
|
||||||
|
query = queries.query_bookmark_tags(self.get_or_create_test_user(), 'term1 term3')
|
||||||
|
self.assertQueryResult(query, [])
|
||||||
|
|
||||||
|
query = queries.query_bookmark_tags(self.get_or_create_test_user(), 'term1 #tag2')
|
||||||
|
self.assertQueryResult(query, [])
|
||||||
|
|
||||||
|
query = queries.query_bookmark_tags(self.get_or_create_test_user(), '#tag3')
|
||||||
|
self.assertQueryResult(query, [])
|
||||||
|
|
||||||
|
# Unused tag
|
||||||
|
query = queries.query_bookmark_tags(self.get_or_create_test_user(), '#unused_tag1')
|
||||||
|
self.assertQueryResult(query, [])
|
||||||
|
|
||||||
|
# Unused tag combined with tag that is used
|
||||||
|
query = queries.query_bookmark_tags(self.get_or_create_test_user(), '#tag1 #unused_tag1')
|
||||||
|
self.assertQueryResult(query, [])
|
||||||
|
|
||||||
|
# Unused tag combined with term that is used
|
||||||
|
query = queries.query_bookmark_tags(self.get_or_create_test_user(), 'term1 #unused_tag1')
|
||||||
|
self.assertQueryResult(query, [])
|
||||||
|
|
||||||
def test_query_bookmark_tags_should_return_tags_for_unarchived_bookmarks_only(self):
|
def test_query_bookmark_tags_should_return_tags_for_unarchived_bookmarks_only(self):
|
||||||
tag1 = self.setup_tag()
|
tag1 = self.setup_tag()
|
||||||
@@ -40,7 +398,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
|
|||||||
|
|
||||||
query = queries.query_bookmark_tags(self.get_or_create_test_user(), '')
|
query = queries.query_bookmark_tags(self.get_or_create_test_user(), '')
|
||||||
|
|
||||||
self.assertCountEqual([tag1], list(query))
|
self.assertQueryResult(query, [[tag1]])
|
||||||
|
|
||||||
def test_query_bookmark_tags_should_return_distinct_tags(self):
|
def test_query_bookmark_tags_should_return_distinct_tags(self):
|
||||||
tag = self.setup_tag()
|
tag = self.setup_tag()
|
||||||
@@ -50,7 +408,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
|
|||||||
|
|
||||||
query = queries.query_bookmark_tags(self.get_or_create_test_user(), '')
|
query = queries.query_bookmark_tags(self.get_or_create_test_user(), '')
|
||||||
|
|
||||||
self.assertCountEqual([tag], list(query))
|
self.assertQueryResult(query, [[tag]])
|
||||||
|
|
||||||
def test_query_archived_bookmark_tags_should_return_tags_for_archived_bookmarks_only(self):
|
def test_query_archived_bookmark_tags_should_return_tags_for_archived_bookmarks_only(self):
|
||||||
tag1 = self.setup_tag()
|
tag1 = self.setup_tag()
|
||||||
@@ -61,7 +419,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
|
|||||||
|
|
||||||
query = queries.query_archived_bookmark_tags(self.get_or_create_test_user(), '')
|
query = queries.query_archived_bookmark_tags(self.get_or_create_test_user(), '')
|
||||||
|
|
||||||
self.assertCountEqual([tag2], list(query))
|
self.assertQueryResult(query, [[tag2]])
|
||||||
|
|
||||||
def test_query_archived_bookmark_tags_should_return_distinct_tags(self):
|
def test_query_archived_bookmark_tags_should_return_distinct_tags(self):
|
||||||
tag = self.setup_tag()
|
tag = self.setup_tag()
|
||||||
@@ -71,4 +429,34 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
|
|||||||
|
|
||||||
query = queries.query_archived_bookmark_tags(self.get_or_create_test_user(), '')
|
query = queries.query_archived_bookmark_tags(self.get_or_create_test_user(), '')
|
||||||
|
|
||||||
self.assertCountEqual([tag], list(query))
|
self.assertQueryResult(query, [[tag]])
|
||||||
|
|
||||||
|
def test_query_bookmark_tags_should_only_return_user_owned_tags(self):
|
||||||
|
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
|
||||||
|
owned_bookmarks = [
|
||||||
|
self.setup_bookmark(tags=[self.setup_tag()]),
|
||||||
|
self.setup_bookmark(tags=[self.setup_tag()]),
|
||||||
|
self.setup_bookmark(tags=[self.setup_tag()]),
|
||||||
|
]
|
||||||
|
self.setup_bookmark(user=other_user, tags=[self.setup_tag(user=other_user)])
|
||||||
|
self.setup_bookmark(user=other_user, tags=[self.setup_tag(user=other_user)])
|
||||||
|
self.setup_bookmark(user=other_user, tags=[self.setup_tag(user=other_user)])
|
||||||
|
|
||||||
|
query = queries.query_bookmark_tags(self.user, '')
|
||||||
|
|
||||||
|
self.assertQueryResult(query, [self.get_tags_from_bookmarks(owned_bookmarks)])
|
||||||
|
|
||||||
|
def test_query_archived_bookmark_tags_should_only_return_user_owned_tags(self):
|
||||||
|
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
|
||||||
|
owned_bookmarks = [
|
||||||
|
self.setup_bookmark(is_archived=True, tags=[self.setup_tag()]),
|
||||||
|
self.setup_bookmark(is_archived=True, tags=[self.setup_tag()]),
|
||||||
|
self.setup_bookmark(is_archived=True, tags=[self.setup_tag()]),
|
||||||
|
]
|
||||||
|
self.setup_bookmark(is_archived=True, user=other_user, tags=[self.setup_tag(user=other_user)])
|
||||||
|
self.setup_bookmark(is_archived=True, user=other_user, tags=[self.setup_tag(user=other_user)])
|
||||||
|
self.setup_bookmark(is_archived=True, user=other_user, tags=[self.setup_tag(user=other_user)])
|
||||||
|
|
||||||
|
query = queries.query_archived_bookmark_tags(self.user, '')
|
||||||
|
|
||||||
|
self.assertQueryResult(query, [self.get_tags_from_bookmarks(owned_bookmarks)])
|
||||||
|
|||||||
40
bookmarks/tests/test_settings_api_view.py
Normal file
40
bookmarks/tests/test_settings_api_view.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
|
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsApiViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
user = self.get_or_create_test_user()
|
||||||
|
self.client.force_login(user)
|
||||||
|
|
||||||
|
def test_should_render_successfully(self):
|
||||||
|
response = self.client.get(reverse('bookmarks:settings.api'))
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_should_check_authentication(self):
|
||||||
|
self.client.logout()
|
||||||
|
response = self.client.get(reverse('bookmarks:settings.api'), follow=True)
|
||||||
|
|
||||||
|
self.assertRedirects(response, reverse('login') + '?next=' + reverse('bookmarks:settings.api'))
|
||||||
|
|
||||||
|
def test_should_generate_api_token_if_not_exists(self):
|
||||||
|
self.assertEqual(Token.objects.count(), 0)
|
||||||
|
|
||||||
|
self.client.get(reverse('bookmarks:settings.api'))
|
||||||
|
|
||||||
|
self.assertEqual(Token.objects.count(), 1)
|
||||||
|
token = Token.objects.first()
|
||||||
|
self.assertEqual(token.user, self.user)
|
||||||
|
|
||||||
|
def test_should_not_generate_api_token_if_exists(self):
|
||||||
|
Token.objects.get_or_create(user=self.user)
|
||||||
|
self.assertEqual(Token.objects.count(), 1)
|
||||||
|
|
||||||
|
self.client.get(reverse('bookmarks:settings.api'))
|
||||||
|
|
||||||
|
self.assertEqual(Token.objects.count(), 1)
|
||||||
45
bookmarks/tests/test_settings_export_view.py
Normal file
45
bookmarks/tests/test_settings_export_view.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsExportViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
user = self.get_or_create_test_user()
|
||||||
|
self.client.force_login(user)
|
||||||
|
|
||||||
|
def assertFormErrorHint(self, response, text: str):
|
||||||
|
self.assertContains(response, '<div class="has-error">')
|
||||||
|
self.assertContains(response, text)
|
||||||
|
|
||||||
|
def test_should_export_successfully(self):
|
||||||
|
self.setup_bookmark(tags=[self.setup_tag()])
|
||||||
|
self.setup_bookmark(tags=[self.setup_tag()])
|
||||||
|
self.setup_bookmark(tags=[self.setup_tag()])
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
reverse('bookmarks:settings.export'),
|
||||||
|
follow=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response['content-type'], 'text/plain; charset=UTF-8')
|
||||||
|
self.assertEqual(response['Content-Disposition'], 'attachment; filename="bookmarks.html"')
|
||||||
|
|
||||||
|
def test_should_check_authentication(self):
|
||||||
|
self.client.logout()
|
||||||
|
response = self.client.get(reverse('bookmarks:settings.export'), follow=True)
|
||||||
|
|
||||||
|
self.assertRedirects(response, reverse('login') + '?next=' + reverse('bookmarks:settings.export'))
|
||||||
|
|
||||||
|
def test_should_show_hint_when_export_raises_error(self):
|
||||||
|
with patch('bookmarks.services.exporter.export_netscape_html') as mock_export_netscape_html:
|
||||||
|
mock_export_netscape_html.side_effect = Exception('Nope')
|
||||||
|
response = self.client.get(reverse('bookmarks:settings.export'), follow=True)
|
||||||
|
|
||||||
|
self.assertTemplateUsed(response, 'settings/general.html')
|
||||||
|
self.assertFormErrorHint(response, 'An error occurred during bookmark export.')
|
||||||
36
bookmarks/tests/test_settings_general_view.py
Normal file
36
bookmarks/tests/test_settings_general_view.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
from bookmarks.models import UserProfile
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
user = self.get_or_create_test_user()
|
||||||
|
self.client.force_login(user)
|
||||||
|
|
||||||
|
def test_should_render_successfully(self):
|
||||||
|
response = self.client.get(reverse('bookmarks:settings.general'))
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_should_check_authentication(self):
|
||||||
|
self.client.logout()
|
||||||
|
response = self.client.get(reverse('bookmarks:settings.general'), follow=True)
|
||||||
|
|
||||||
|
self.assertRedirects(response, reverse('login') + '?next=' + reverse('bookmarks:settings.general'))
|
||||||
|
|
||||||
|
def test_should_save_profile(self):
|
||||||
|
form_data = {
|
||||||
|
'theme': UserProfile.THEME_DARK,
|
||||||
|
'bookmark_date_display': UserProfile.BOOKMARK_DATE_DISPLAY_HIDDEN,
|
||||||
|
}
|
||||||
|
response = self.client.post(reverse('bookmarks:settings.general'), form_data)
|
||||||
|
|
||||||
|
self.user.profile.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(self.user.profile.theme, form_data['theme'])
|
||||||
|
self.assertEqual(self.user.profile.bookmark_date_display, form_data['bookmark_date_display'])
|
||||||
77
bookmarks/tests/test_settings_import_view.py
Normal file
77
bookmarks/tests/test_settings_import_view.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsImportViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
user = self.get_or_create_test_user()
|
||||||
|
self.client.force_login(user)
|
||||||
|
|
||||||
|
def assertFormSuccessHint(self, response, text: str):
|
||||||
|
self.assertContains(response, '<div class="has-success">')
|
||||||
|
self.assertContains(response, text)
|
||||||
|
|
||||||
|
def assertNoFormSuccessHint(self, response):
|
||||||
|
self.assertNotContains(response, '<div class="has-success">')
|
||||||
|
|
||||||
|
def assertFormErrorHint(self, response, text: str):
|
||||||
|
self.assertContains(response, '<div class="has-error">')
|
||||||
|
self.assertContains(response, text)
|
||||||
|
|
||||||
|
def assertNoFormErrorHint(self, response):
|
||||||
|
self.assertNotContains(response, '<div class="has-error">')
|
||||||
|
|
||||||
|
def test_should_import_successfully(self):
|
||||||
|
with open('bookmarks/tests/resources/simple_valid_import_file.html') as import_file:
|
||||||
|
response = self.client.post(
|
||||||
|
reverse('bookmarks:settings.import'),
|
||||||
|
{'import_file': import_file},
|
||||||
|
follow=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRedirects(response, reverse('bookmarks:settings.general'))
|
||||||
|
self.assertFormSuccessHint(response, '3 bookmarks were successfully imported')
|
||||||
|
self.assertNoFormErrorHint(response)
|
||||||
|
|
||||||
|
def test_should_check_authentication(self):
|
||||||
|
self.client.logout()
|
||||||
|
response = self.client.get(reverse('bookmarks:settings.import'), follow=True)
|
||||||
|
|
||||||
|
self.assertRedirects(response, reverse('login') + '?next=' + reverse('bookmarks:settings.import'))
|
||||||
|
|
||||||
|
def test_should_show_hint_if_there_is_no_file(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse('bookmarks:settings.import'),
|
||||||
|
follow=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRedirects(response, reverse('bookmarks:settings.general'))
|
||||||
|
self.assertNoFormSuccessHint(response)
|
||||||
|
self.assertFormErrorHint(response, 'Please select a file to import.')
|
||||||
|
|
||||||
|
def test_should_show_hint_if_import_raises_exception(self):
|
||||||
|
with open('bookmarks/tests/resources/invalid_import_file.png', 'rb') as import_file:
|
||||||
|
response = self.client.post(
|
||||||
|
reverse('bookmarks:settings.import'),
|
||||||
|
{'import_file': import_file},
|
||||||
|
follow=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRedirects(response, reverse('bookmarks:settings.general'))
|
||||||
|
self.assertNoFormSuccessHint(response)
|
||||||
|
self.assertFormErrorHint(response, 'An error occurred during bookmark import.')
|
||||||
|
|
||||||
|
def test_should_show_respective_hints_if_not_all_bookmarks_were_imported_successfully(self):
|
||||||
|
with open('bookmarks/tests/resources/simple_valid_import_file_with_one_invalid_bookmark.html') as import_file:
|
||||||
|
response = self.client.post(
|
||||||
|
reverse('bookmarks:settings.import'),
|
||||||
|
{'import_file': import_file},
|
||||||
|
follow=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRedirects(response, reverse('bookmarks:settings.general'))
|
||||||
|
self.assertFormSuccessHint(response, '2 bookmarks were successfully imported')
|
||||||
|
self.assertFormErrorHint(response, '1 bookmarks could not be imported')
|
||||||
22
bookmarks/tests/test_settings_integrations_view.py
Normal file
22
bookmarks/tests/test_settings_integrations_view.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsIntegrationsViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
user = self.get_or_create_test_user()
|
||||||
|
self.client.force_login(user)
|
||||||
|
|
||||||
|
def test_should_render_successfully(self):
|
||||||
|
response = self.client.get(reverse('bookmarks:settings.integrations'))
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_should_check_authentication(self):
|
||||||
|
self.client.logout()
|
||||||
|
response = self.client.get(reverse('bookmarks:settings.integrations'), follow=True)
|
||||||
|
|
||||||
|
self.assertRedirects(response, reverse('login') + '?next=' + reverse('bookmarks:settings.integrations'))
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django.test import TestCase
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
from bookmarks.models import UserProfile
|
from bookmarks.models import UserProfile
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ from rest_framework.authtoken.models import Token
|
|||||||
|
|
||||||
from bookmarks.models import UserProfileForm
|
from bookmarks.models import UserProfileForm
|
||||||
from bookmarks.queries import query_bookmarks
|
from bookmarks.queries import query_bookmarks
|
||||||
from bookmarks.services.exporter import export_netscape_html
|
from bookmarks.services import exporter
|
||||||
from bookmarks.services.importer import import_netscape_html
|
from bookmarks.services import importer
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -55,11 +55,11 @@ def bookmark_import(request):
|
|||||||
|
|
||||||
if import_file is None:
|
if import_file is None:
|
||||||
messages.error(request, 'Please select a file to import.', 'bookmark_import_errors')
|
messages.error(request, 'Please select a file to import.', 'bookmark_import_errors')
|
||||||
return HttpResponseRedirect(reverse('bookmarks:settings.index'))
|
return HttpResponseRedirect(reverse('bookmarks:settings.general'))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
content = import_file.read().decode()
|
content = import_file.read().decode()
|
||||||
result = import_netscape_html(content, request.user)
|
result = importer.import_netscape_html(content, request.user)
|
||||||
success_msg = str(result.success) + ' bookmarks were successfully imported.'
|
success_msg = str(result.success) + ' bookmarks were successfully imported.'
|
||||||
messages.success(request, success_msg, 'bookmark_import_success')
|
messages.success(request, success_msg, 'bookmark_import_success')
|
||||||
if result.failed > 0:
|
if result.failed > 0:
|
||||||
@@ -78,7 +78,7 @@ def bookmark_export(request):
|
|||||||
# noinspection PyBroadException
|
# noinspection PyBroadException
|
||||||
try:
|
try:
|
||||||
bookmarks = query_bookmarks(request.user, '')
|
bookmarks = query_bookmarks(request.user, '')
|
||||||
file_content = export_netscape_html(bookmarks)
|
file_content = exporter.export_netscape_html(bookmarks)
|
||||||
|
|
||||||
response = HttpResponse(content_type='text/plain; charset=UTF-8')
|
response = HttpResponse(content_type='text/plain; charset=UTF-8')
|
||||||
response['Content-Disposition'] = 'attachment; filename="bookmarks.html"'
|
response['Content-Disposition'] = 'attachment; filename="bookmarks.html"'
|
||||||
|
|||||||
5
coverage.sh
Executable file
5
coverage.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
coverage erase
|
||||||
|
coverage run manage.py test
|
||||||
|
coverage report --sort=cover
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "linkding",
|
"name": "linkding",
|
||||||
"version": "1.4.0",
|
"version": "1.6.5",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ beautifulsoup4==4.7.1
|
|||||||
certifi==2019.6.16
|
certifi==2019.6.16
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
confusable-homoglyphs==3.2.0
|
confusable-homoglyphs==3.2.0
|
||||||
|
coverage==5.5
|
||||||
Django==2.2.20
|
Django==2.2.20
|
||||||
django-appconf==1.0.3
|
django-appconf==1.0.3
|
||||||
django-compressor==2.3
|
django-compressor==2.3
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.6.4
|
1.6.5
|
||||||
|
|||||||
Reference in New Issue
Block a user