init
This commit is contained in:
@@ -1,3 +1,67 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework.test import APITestCase, APIClient
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
# Create your tests here.
|
from .routers import router
|
||||||
|
|
||||||
|
|
||||||
|
class RouterTestCase(TestCase):
|
||||||
|
def test_router_urls(self):
|
||||||
|
"""Test that the router generates the expected URL patterns"""
|
||||||
|
url_patterns = router.urls
|
||||||
|
|
||||||
|
# Check that all expected viewsets are registered
|
||||||
|
expected_basenames = ['user', 'torrent', 'file', 'friend-request']
|
||||||
|
registered_basenames = [url.name.split('-')[0] for url in url_patterns if '-list' in url.name]
|
||||||
|
|
||||||
|
for basename in expected_basenames:
|
||||||
|
self.assertIn(basename, registered_basenames)
|
||||||
|
|
||||||
|
# Check that list and detail URLs are generated for each viewset
|
||||||
|
for basename in expected_basenames:
|
||||||
|
list_url_name = f"{basename}-list"
|
||||||
|
detail_url_name = f"{basename}-detail"
|
||||||
|
|
||||||
|
self.assertTrue(any(url.name == list_url_name for url in url_patterns))
|
||||||
|
self.assertTrue(any(url.name == detail_url_name for url in url_patterns))
|
||||||
|
|
||||||
|
|
||||||
|
class APIEndpointsTestCase(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
from user.models import User
|
||||||
|
|
||||||
|
# Create a test user
|
||||||
|
self.user = User.objects.create_user(
|
||||||
|
username='testuser',
|
||||||
|
email='test@example.com',
|
||||||
|
password='testpassword'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Authenticate the client
|
||||||
|
self.client = APIClient()
|
||||||
|
self.client.force_authenticate(user=self.user)
|
||||||
|
|
||||||
|
def test_user_endpoint(self):
|
||||||
|
"""Test that the users endpoint is accessible"""
|
||||||
|
url = reverse('user-list')
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_torrent_endpoint(self):
|
||||||
|
"""Test that the torrents endpoint is accessible"""
|
||||||
|
url = reverse('torrent-list')
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_file_endpoint(self):
|
||||||
|
"""Test that the files endpoint is accessible"""
|
||||||
|
url = reverse('file-list')
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_friend_request_endpoint(self):
|
||||||
|
"""Test that the friend requests endpoint is accessible"""
|
||||||
|
url = reverse('friendrequest-list')
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<v-divider/>
|
<v-divider/>
|
||||||
<UserStats :stats="user_stats"/>
|
<UserStats :stats="user_stats"/>
|
||||||
<v-divider/>
|
<v-divider/>
|
||||||
{{dm_status}}
|
<!-- {{dm_status}}-->
|
||||||
<DM v-if="dm_activated" :dm_files="dm_files" :dm_status="dm_status" :dm_download_location="dm_download_location"/>
|
<DM v-if="dm_activated" :dm_files="dm_files" :dm_status="dm_status" :dm_download_location="dm_download_location"/>
|
||||||
</v-navigation-drawer>
|
</v-navigation-drawer>
|
||||||
<v-navigation-drawer v-model="right_drawer" location="right" temporary>
|
<v-navigation-drawer v-model="right_drawer" location="right" temporary>
|
||||||
@@ -157,6 +157,7 @@ export default {
|
|||||||
this.dm_status = data;
|
this.dm_status = data;
|
||||||
})
|
})
|
||||||
this.$qt.on("upload_torrent", data => {
|
this.$qt.on("upload_torrent", data => {
|
||||||
|
console.log(typeof data);
|
||||||
this.uploadTorrentB64(data);
|
this.uploadTorrentB64(data);
|
||||||
})
|
})
|
||||||
this.$qt.on("files_updated", data => {
|
this.$qt.on("files_updated", data => {
|
||||||
@@ -220,7 +221,8 @@ export default {
|
|||||||
},
|
},
|
||||||
async uploadTorrentB64(b64_torrent){
|
async uploadTorrentB64(b64_torrent){
|
||||||
let form = new FormData();
|
let form = new FormData();
|
||||||
form.append("torrent", b64_torrent);
|
form.append("file", b64_torrent);
|
||||||
|
form.append("file_mode", "base64");
|
||||||
let response = await fetch("/api/torrents/", {
|
let response = await fetch("/api/torrents/", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -6,12 +6,19 @@
|
|||||||
</v-dialog>
|
</v-dialog>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
:prepend-icon="dm_status.pause ? 'mdi-play' : 'mdi-pause'"
|
:prepend-icon="dm_status.pause ? 'mdi-play' : 'mdi-pause'"
|
||||||
@click="$qt.callMethod('set_pause', !dm_status.pause)"
|
@click="setPause(!dm_status.pause)"
|
||||||
|
:disabled="!start_available"
|
||||||
>
|
>
|
||||||
<template v-slot:title>
|
<template v-slot:title>
|
||||||
{{ dm_status.pause ? 'Start':'Pause' }}
|
{{ dm_status.pause ? 'Start':'Pause' }}
|
||||||
</template>
|
</template>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
<v-list-item
|
||||||
|
prepend-icon="mdi-delete-sweep"
|
||||||
|
:disabled="!dm_status.pause"
|
||||||
|
@click="$qt.callMethod('del_files', Object.keys(dm_files))"
|
||||||
|
title="Clear"
|
||||||
|
/>
|
||||||
<v-list-item prepend-icon="mdi-speedometer">
|
<v-list-item prepend-icon="mdi-speedometer">
|
||||||
<template v-slot:title>
|
<template v-slot:title>
|
||||||
{{fs_speed_format(dm_status.speed)}}/s
|
{{fs_speed_format(dm_status.speed)}}/s
|
||||||
@@ -42,7 +49,7 @@ import {fs_format, fs_speed_format} from "@/plugins/utils.js";
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
methods: {fs_format, fs_speed_format},
|
|
||||||
props: {
|
props: {
|
||||||
dm_files: {
|
dm_files: {
|
||||||
required: true,
|
required: true,
|
||||||
@@ -60,6 +67,20 @@ export default {
|
|||||||
data(){
|
data(){
|
||||||
return {
|
return {
|
||||||
details_enabled: false,
|
details_enabled: false,
|
||||||
|
start_available: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fs_format, fs_speed_format,
|
||||||
|
setPause(pause){
|
||||||
|
this.start_available = false
|
||||||
|
this.$qt.callMethod('set_pause', pause);
|
||||||
|
if(pause){
|
||||||
|
setTimeout(() => {this.start_available = true;}, 1000);
|
||||||
|
}else{
|
||||||
|
this.start_available = true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
{{ file.rel_path }}
|
{{ file.rel_path }}
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:subtitle>
|
<template v-slot:subtitle>
|
||||||
<v-progress-linear
|
<v-progress-linear :model-value="file.stats.percent" height="14" color="blue">
|
||||||
:model-value="file.stats.percent" height="12" color="blue">
|
{{file.stats.percent.toFixed(1)}}% ({{fs_speed_format(file.stats.speed)}}/s)
|
||||||
</v-progress-linear>
|
</v-progress-linear>
|
||||||
</template>
|
</template>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
@@ -97,9 +97,10 @@ export default {
|
|||||||
for(const [file_id, file] of Object.entries(this.dm_files)){
|
for(const [file_id, file] of Object.entries(this.dm_files)){
|
||||||
if(file.downloaded){
|
if(file.downloaded){
|
||||||
finished[file_id] = file;
|
finished[file_id] = file;
|
||||||
}else if(file_id in this.dm_status.downloading){
|
}else if(this.dm_status.downloading.includes(file_id)){
|
||||||
downloading[file_id] = file;
|
downloading[file_id] = file;
|
||||||
if(file_id in this.dm_status["downloader_stats"]){
|
if(file_id in this.dm_status["downloader_stats"]){
|
||||||
|
|
||||||
downloading[file_id]["stats"] = this.dm_status["downloader_stats"][file_id];
|
downloading[file_id]["stats"] = this.dm_status["downloader_stats"][file_id];
|
||||||
}else{
|
}else{
|
||||||
downloading[file_id]["stats"] = {
|
downloading[file_id]["stats"] = {
|
||||||
|
|||||||
@@ -1,3 +1,328 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework.test import APITestCase, APIClient
|
||||||
|
from rest_framework import status
|
||||||
|
from django.conf import settings
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
# Create your tests here.
|
from .models import Torrent, SharedUser, File
|
||||||
|
from user.models import User
|
||||||
|
from .views import TorrentViewSet, FileViewSet
|
||||||
|
from .utils import Transmission, torrent_proceed, torrent_share
|
||||||
|
|
||||||
|
|
||||||
|
class TorrentModelTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.user = User.objects.create_user(
|
||||||
|
username='testuser',
|
||||||
|
email='test@example.com',
|
||||||
|
password='testpassword'
|
||||||
|
)
|
||||||
|
self.torrent = Torrent.objects.create(
|
||||||
|
id='abc123',
|
||||||
|
name='Test Torrent',
|
||||||
|
user=self.user,
|
||||||
|
size=1000,
|
||||||
|
transmission_data={}
|
||||||
|
)
|
||||||
|
self.shared_user = User.objects.create_user(
|
||||||
|
username='shareduser',
|
||||||
|
email='shared@example.com',
|
||||||
|
password='sharedpassword'
|
||||||
|
)
|
||||||
|
self.file = File.objects.create(
|
||||||
|
torrent=self.torrent,
|
||||||
|
rel_name='test_file.txt',
|
||||||
|
size=100
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_len_files(self):
|
||||||
|
"""Test the len_files property returns the correct count of files"""
|
||||||
|
self.assertEqual(self.torrent.len_files, 1)
|
||||||
|
|
||||||
|
# Add another file and test again
|
||||||
|
File.objects.create(
|
||||||
|
torrent=self.torrent,
|
||||||
|
rel_name='another_file.txt',
|
||||||
|
size=200
|
||||||
|
)
|
||||||
|
|
||||||
|
# Clear cached_property
|
||||||
|
if hasattr(self.torrent, '_len_files'):
|
||||||
|
delattr(self.torrent, '_len_files')
|
||||||
|
|
||||||
|
self.assertEqual(self.torrent.len_files, 2)
|
||||||
|
|
||||||
|
def test_related_users(self):
|
||||||
|
"""Test the related_users property returns the correct list of users"""
|
||||||
|
# Initially only the owner
|
||||||
|
self.assertEqual(self.torrent.related_users, [self.user.id])
|
||||||
|
|
||||||
|
# Add a shared user
|
||||||
|
self.torrent.shared_users.add(self.shared_user)
|
||||||
|
|
||||||
|
# Clear cached_property
|
||||||
|
if hasattr(self.torrent, '_related_users'):
|
||||||
|
delattr(self.torrent, '_related_users')
|
||||||
|
|
||||||
|
# Should include both users now
|
||||||
|
self.assertIn(self.user.id, self.torrent.related_users)
|
||||||
|
self.assertIn(self.shared_user.id, self.torrent.related_users)
|
||||||
|
|
||||||
|
|
||||||
|
class FileModelTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.user = User.objects.create_user(
|
||||||
|
username='testuser',
|
||||||
|
email='test@example.com',
|
||||||
|
password='testpassword'
|
||||||
|
)
|
||||||
|
self.torrent = Torrent.objects.create(
|
||||||
|
id='abc123',
|
||||||
|
name='Test Torrent',
|
||||||
|
user=self.user,
|
||||||
|
size=1000,
|
||||||
|
transmission_data={}
|
||||||
|
)
|
||||||
|
self.file = File.objects.create(
|
||||||
|
torrent=self.torrent,
|
||||||
|
rel_name='test/path/file.mp4',
|
||||||
|
size=100
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_pathname(self):
|
||||||
|
"""Test the pathname property returns the correct path"""
|
||||||
|
self.assertEqual(str(self.file.pathname), 'test/path/file.mp4')
|
||||||
|
|
||||||
|
def test_filename(self):
|
||||||
|
"""Test the filename property returns the correct filename"""
|
||||||
|
self.assertEqual(self.file.filename, 'file.mp4')
|
||||||
|
|
||||||
|
def test_abs_pathname(self):
|
||||||
|
"""Test the abs_pathname property returns the correct absolute path"""
|
||||||
|
expected_path = settings.DOWNLOAD_BASE_DIR / self.file.pathname
|
||||||
|
self.assertEqual(self.file.abs_pathname, expected_path)
|
||||||
|
|
||||||
|
def test_is_video(self):
|
||||||
|
"""Test the is_video property correctly identifies video files"""
|
||||||
|
self.assertTrue(self.file.is_video) # mp4 should be identified as video
|
||||||
|
|
||||||
|
# Test non-video file
|
||||||
|
non_video_file = File.objects.create(
|
||||||
|
torrent=self.torrent,
|
||||||
|
rel_name='test/path/document.pdf',
|
||||||
|
size=50
|
||||||
|
)
|
||||||
|
self.assertFalse(non_video_file.is_video)
|
||||||
|
|
||||||
|
|
||||||
|
class SharedUserModelTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.owner = User.objects.create_user(
|
||||||
|
username='owner',
|
||||||
|
email='owner@example.com',
|
||||||
|
password='ownerpassword'
|
||||||
|
)
|
||||||
|
self.shared_user = User.objects.create_user(
|
||||||
|
username='shareduser',
|
||||||
|
email='shared@example.com',
|
||||||
|
password='sharedpassword'
|
||||||
|
)
|
||||||
|
self.torrent = Torrent.objects.create(
|
||||||
|
id='abc123',
|
||||||
|
name='Test Torrent',
|
||||||
|
user=self.owner,
|
||||||
|
size=1000,
|
||||||
|
transmission_data={}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_shared_user_creation(self):
|
||||||
|
"""Test creating a shared user relationship"""
|
||||||
|
shared = SharedUser.objects.create(
|
||||||
|
user=self.shared_user,
|
||||||
|
torrent=self.torrent
|
||||||
|
)
|
||||||
|
self.assertEqual(shared.user, self.shared_user)
|
||||||
|
self.assertEqual(shared.torrent, self.torrent)
|
||||||
|
|
||||||
|
# Verify the relationship is reflected in the torrent's shared_users
|
||||||
|
self.assertIn(self.shared_user, self.torrent.shared_users.all())
|
||||||
|
|
||||||
|
|
||||||
|
class TorrentViewSetTestCase(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.user = User.objects.create_user(
|
||||||
|
username='testuser',
|
||||||
|
email='test@example.com',
|
||||||
|
password='testpassword'
|
||||||
|
)
|
||||||
|
self.client = APIClient()
|
||||||
|
self.client.force_authenticate(user=self.user)
|
||||||
|
|
||||||
|
self.torrent = Torrent.objects.create(
|
||||||
|
id='abc123',
|
||||||
|
name='Test Torrent',
|
||||||
|
user=self.user,
|
||||||
|
size=1000,
|
||||||
|
transmission_data={}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.file = File.objects.create(
|
||||||
|
torrent=self.torrent,
|
||||||
|
rel_name='test_file.txt',
|
||||||
|
size=100
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_list_torrents(self):
|
||||||
|
"""Test listing torrents"""
|
||||||
|
url = reverse('torrent-list')
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(len(response.data), 1)
|
||||||
|
self.assertEqual(response.data[0]['id'], self.torrent.id)
|
||||||
|
|
||||||
|
def test_retrieve_torrent(self):
|
||||||
|
"""Test retrieving a specific torrent"""
|
||||||
|
url = reverse('torrent-detail', args=[self.torrent.id])
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(response.data['id'], self.torrent.id)
|
||||||
|
self.assertEqual(response.data['name'], 'Test Torrent')
|
||||||
|
|
||||||
|
@patch('torrent.views.torrent_share')
|
||||||
|
def test_share_torrent(self, mock_torrent_share):
|
||||||
|
"""Test sharing a torrent with another user"""
|
||||||
|
mock_torrent_share.return_value = True
|
||||||
|
|
||||||
|
shared_user = User.objects.create_user(
|
||||||
|
username='shareduser',
|
||||||
|
email='shared@example.com',
|
||||||
|
password='sharedpassword'
|
||||||
|
)
|
||||||
|
|
||||||
|
url = reverse('torrent-share', args=[self.torrent.id])
|
||||||
|
response = self.client.post(url, {'user_id': shared_user.id})
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertTrue(response.data['success'])
|
||||||
|
mock_torrent_share.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
class FileViewSetTestCase(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.user = User.objects.create_user(
|
||||||
|
username='testuser',
|
||||||
|
email='test@example.com',
|
||||||
|
password='testpassword'
|
||||||
|
)
|
||||||
|
self.client = APIClient()
|
||||||
|
self.client.force_authenticate(user=self.user)
|
||||||
|
|
||||||
|
self.torrent = Torrent.objects.create(
|
||||||
|
id='abc123',
|
||||||
|
name='Test Torrent',
|
||||||
|
user=self.user,
|
||||||
|
size=1000,
|
||||||
|
transmission_data={}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.file = File.objects.create(
|
||||||
|
torrent=self.torrent,
|
||||||
|
rel_name='test_file.txt',
|
||||||
|
size=100
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_list_files(self):
|
||||||
|
"""Test listing files"""
|
||||||
|
url = reverse('file-list')
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_retrieve_file(self):
|
||||||
|
"""Test retrieving a specific file"""
|
||||||
|
url = reverse('file-detail', args=[self.file.id])
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(response.data['id'], str(self.file.id))
|
||||||
|
self.assertEqual(response.data['rel_name'], 'test_file.txt')
|
||||||
|
|
||||||
|
|
||||||
|
class TransmissionUtilsTestCase(TestCase):
|
||||||
|
@patch('torrent.utils.Client')
|
||||||
|
def test_transmission_init(self, mock_client):
|
||||||
|
"""Test Transmission class initialization"""
|
||||||
|
transmission = Transmission()
|
||||||
|
mock_client.assert_called_once_with(**settings.TRANSMISSION)
|
||||||
|
|
||||||
|
@patch('torrent.utils.Client')
|
||||||
|
def test_add_torrent(self, mock_client):
|
||||||
|
"""Test adding a torrent"""
|
||||||
|
mock_instance = mock_client.return_value
|
||||||
|
mock_instance.add_torrent.return_value = MagicMock()
|
||||||
|
|
||||||
|
transmission = Transmission()
|
||||||
|
file_obj = MagicMock()
|
||||||
|
result = transmission.add_torrent(file_obj)
|
||||||
|
|
||||||
|
mock_instance.add_torrent.assert_called_once_with(file_obj)
|
||||||
|
self.assertEqual(result, mock_instance.add_torrent.return_value)
|
||||||
|
|
||||||
|
@patch('torrent.utils.Client')
|
||||||
|
def test_get_data(self, mock_client):
|
||||||
|
"""Test getting torrent data"""
|
||||||
|
mock_instance = mock_client.return_value
|
||||||
|
mock_torrent = MagicMock()
|
||||||
|
mock_torrent.progress = 50
|
||||||
|
mock_torrent.fields = {'name': 'Test', 'size': 1000}
|
||||||
|
mock_instance.get_torrent.return_value = mock_torrent
|
||||||
|
|
||||||
|
transmission = Transmission()
|
||||||
|
result = transmission.get_data('hash123')
|
||||||
|
|
||||||
|
mock_instance.get_torrent.assert_called_once_with('hash123', transmission.trpc_args)
|
||||||
|
self.assertEqual(result['progress'], 50)
|
||||||
|
self.assertEqual(result['name'], 'Test')
|
||||||
|
self.assertEqual(result['size'], 1000)
|
||||||
|
|
||||||
|
|
||||||
|
class TorrentProceedTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.user = User.objects.create_user(
|
||||||
|
username='testuser',
|
||||||
|
email='test@example.com',
|
||||||
|
password='testpassword',
|
||||||
|
max_size=10000
|
||||||
|
)
|
||||||
|
|
||||||
|
@patch('torrent.utils.transmission_handler')
|
||||||
|
def test_torrent_proceed_size_exceed(self, mock_transmission):
|
||||||
|
"""Test torrent_proceed when user size is exceeded"""
|
||||||
|
# Set user's used size to exceed max_size
|
||||||
|
self.user.max_size = 100
|
||||||
|
Torrent.objects.create(
|
||||||
|
id='abc123',
|
||||||
|
name='Test Torrent',
|
||||||
|
user=self.user,
|
||||||
|
size=200, # Exceeds max_size
|
||||||
|
transmission_data={}
|
||||||
|
)
|
||||||
|
|
||||||
|
file_obj = MagicMock()
|
||||||
|
result = torrent_proceed(self.user, file_obj)
|
||||||
|
|
||||||
|
self.assertEqual(result['status'], 'error')
|
||||||
|
self.assertEqual(result['message'], 'Size exceed')
|
||||||
|
mock_transmission.add_torrent.assert_not_called()
|
||||||
|
|
||||||
|
@patch('torrent.utils.transmission_handler')
|
||||||
|
def test_torrent_proceed_transmission_error(self, mock_transmission):
|
||||||
|
"""Test torrent_proceed when transmission raises an error"""
|
||||||
|
from transmission_rpc.error import TransmissionError
|
||||||
|
|
||||||
|
mock_transmission.add_torrent.side_effect = TransmissionError('Test error')
|
||||||
|
|
||||||
|
file_obj = MagicMock()
|
||||||
|
result = torrent_proceed(self.user, file_obj)
|
||||||
|
|
||||||
|
self.assertEqual(result['status'], 'error')
|
||||||
|
self.assertEqual(result['message'], 'Transmission Error')
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
import traceback
|
import traceback
|
||||||
|
import base64
|
||||||
|
import io
|
||||||
|
|
||||||
from transmission_rpc import Client
|
from transmission_rpc import Client
|
||||||
from transmission_rpc.error import TransmissionError
|
from transmission_rpc.error import TransmissionError
|
||||||
|
|
||||||
@@ -18,8 +21,15 @@ class Transmission:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.client = Client(**settings.TRANSMISSION)
|
self.client = Client(**settings.TRANSMISSION)
|
||||||
|
|
||||||
def add_torrent(self, file):
|
def add_torrent(self, file, file_mode="file_object"):
|
||||||
|
match file_mode:
|
||||||
|
case "file_object":
|
||||||
return self.client.add_torrent(file)
|
return self.client.add_torrent(file)
|
||||||
|
case "base64":
|
||||||
|
file_content = base64.b64decode(file)
|
||||||
|
file_obj = io.BytesIO(file_content)
|
||||||
|
return self.client.add_torrent(file_obj)
|
||||||
|
return None
|
||||||
|
|
||||||
def get_data(self, hash_string):
|
def get_data(self, hash_string):
|
||||||
data = self.client.get_torrent(hash_string, self.trpc_args)
|
data = self.client.get_torrent(hash_string, self.trpc_args)
|
||||||
@@ -45,7 +55,7 @@ class Transmission:
|
|||||||
transmission_handler = Transmission()
|
transmission_handler = Transmission()
|
||||||
|
|
||||||
|
|
||||||
def torrent_proceed(user, file):
|
def torrent_proceed(user, file, file_mode="file_object"):
|
||||||
r = {
|
r = {
|
||||||
"torrent": None,
|
"torrent": None,
|
||||||
"status": "error",
|
"status": "error",
|
||||||
@@ -58,7 +68,7 @@ def torrent_proceed(user, file):
|
|||||||
return r
|
return r
|
||||||
|
|
||||||
try:
|
try:
|
||||||
torrent_uploaded = transmission_handler.add_torrent(file)
|
torrent_uploaded = transmission_handler.add_torrent(file, file_mode=file_mode)
|
||||||
except TransmissionError:
|
except TransmissionError:
|
||||||
print(traceback.format_exc())
|
print(traceback.format_exc())
|
||||||
r["message"] = "Transmission Error"
|
r["message"] = "Transmission Error"
|
||||||
@@ -74,7 +84,7 @@ def torrent_proceed(user, file):
|
|||||||
if torrent.user == user:
|
if torrent.user == user:
|
||||||
r["message"] = "Already exist"
|
r["message"] = "Already exist"
|
||||||
return r
|
return r
|
||||||
elif torrent.shared_users.filter(user=user).exists():
|
elif torrent.shared_users.filter(id=user.id).exists():
|
||||||
r["message"] = "Already shared"
|
r["message"] = "Already shared"
|
||||||
return r
|
return r
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from django.shortcuts import redirect
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.db.models import Q, Count, OuterRef
|
from django.db.models import Q, Count, OuterRef, Sum
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.http import HttpResponse, Http404, StreamingHttpResponse
|
from django.http import HttpResponse, Http404, StreamingHttpResponse
|
||||||
|
|
||||||
@@ -152,7 +152,9 @@ class TorrentViewSet(mixins.CreateModelMixin,
|
|||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
file = request.data["file"]
|
file = request.data["file"]
|
||||||
r = torrent_proceed(self.request.user, file)
|
file_mode = request.data.get("file_mode", "file_object")
|
||||||
|
r = torrent_proceed(self.request.user, file, file_mode)
|
||||||
|
|
||||||
if r["torrent"]:
|
if r["torrent"]:
|
||||||
r["torrent"] = self.get_serializer_class()(r["torrent"]).data
|
r["torrent"] = self.get_serializer_class()(r["torrent"]).data
|
||||||
return Response(r)
|
return Response(r)
|
||||||
|
|||||||
@@ -1,3 +1,316 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework.test import APITestCase, APIClient
|
||||||
|
from rest_framework import status
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
# Create your tests here.
|
from .models import User, FriendRequest, Invitation, UsernameUserManager
|
||||||
|
from torrent.models import Torrent
|
||||||
|
from .views import UserViewSet, FriendRequestViewSet
|
||||||
|
|
||||||
|
|
||||||
|
class UserModelTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.user = User.objects.create_user(
|
||||||
|
username='testuser',
|
||||||
|
email='test@example.com',
|
||||||
|
password='testpassword',
|
||||||
|
max_size=1000000
|
||||||
|
)
|
||||||
|
self.friend = User.objects.create_user(
|
||||||
|
username='frienduser',
|
||||||
|
email='friend@example.com',
|
||||||
|
password='friendpassword'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_size_used_property(self):
|
||||||
|
"""Test the size_used property returns the correct total size of user's torrents"""
|
||||||
|
# Initially no torrents, so size should be 0
|
||||||
|
self.assertEqual(self.user.size_used, 0)
|
||||||
|
|
||||||
|
# Create a torrent for the user
|
||||||
|
Torrent.objects.create(
|
||||||
|
id='abc123',
|
||||||
|
name='Test Torrent',
|
||||||
|
user=self.user,
|
||||||
|
size=5000,
|
||||||
|
transmission_data={}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create another torrent
|
||||||
|
Torrent.objects.create(
|
||||||
|
id='def456',
|
||||||
|
name='Another Torrent',
|
||||||
|
user=self.user,
|
||||||
|
size=3000,
|
||||||
|
transmission_data={}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Clear cached_property if it exists
|
||||||
|
if hasattr(self.user, 'total_size'):
|
||||||
|
delattr(self.user, 'total_size')
|
||||||
|
|
||||||
|
# Size used should be the sum of torrent sizes
|
||||||
|
self.assertEqual(self.user.size_used, 8000)
|
||||||
|
|
||||||
|
def test_min_infos_property(self):
|
||||||
|
"""Test the min_infos property returns the correct user info"""
|
||||||
|
expected_info = {
|
||||||
|
'username': 'testuser',
|
||||||
|
'id': self.user.id
|
||||||
|
}
|
||||||
|
self.assertEqual(self.user.min_infos, expected_info)
|
||||||
|
|
||||||
|
|
||||||
|
class UsernameUserManagerTestCase(TestCase):
|
||||||
|
def test_create_user(self):
|
||||||
|
"""Test creating a regular user"""
|
||||||
|
user = User.objects.create_user(
|
||||||
|
username='newuser',
|
||||||
|
email='new@example.com',
|
||||||
|
password='newpassword'
|
||||||
|
)
|
||||||
|
self.assertFalse(user.is_staff)
|
||||||
|
self.assertFalse(user.is_superuser)
|
||||||
|
self.assertEqual(user.username, 'newuser')
|
||||||
|
self.assertEqual(user.email, 'new@example.com')
|
||||||
|
self.assertTrue(user.check_password('newpassword'))
|
||||||
|
|
||||||
|
def test_create_superuser(self):
|
||||||
|
"""Test creating a superuser"""
|
||||||
|
admin = User.objects.create_superuser(
|
||||||
|
username='admin',
|
||||||
|
email='admin@example.com',
|
||||||
|
password='adminpassword'
|
||||||
|
)
|
||||||
|
self.assertTrue(admin.is_staff)
|
||||||
|
self.assertTrue(admin.is_superuser)
|
||||||
|
self.assertEqual(admin.username, 'admin')
|
||||||
|
self.assertEqual(admin.email, 'admin@example.com')
|
||||||
|
|
||||||
|
def test_create_user_without_username(self):
|
||||||
|
"""Test creating a user without username raises error"""
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
User.objects.create_user(
|
||||||
|
username='',
|
||||||
|
email='test@example.com',
|
||||||
|
password='testpassword'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_create_user_without_email(self):
|
||||||
|
"""Test creating a user without email raises error"""
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
User.objects.create_user(
|
||||||
|
username='testuser',
|
||||||
|
email='',
|
||||||
|
password='testpassword'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FriendRequestModelTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.sender = User.objects.create_user(
|
||||||
|
username='sender',
|
||||||
|
email='sender@example.com',
|
||||||
|
password='senderpassword'
|
||||||
|
)
|
||||||
|
self.receiver = User.objects.create_user(
|
||||||
|
username='receiver',
|
||||||
|
email='receiver@example.com',
|
||||||
|
password='receiverpassword'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_friend_request_creation(self):
|
||||||
|
"""Test creating a friend request"""
|
||||||
|
friend_request = FriendRequest.objects.create(
|
||||||
|
sender=self.sender,
|
||||||
|
receiver=self.receiver
|
||||||
|
)
|
||||||
|
self.assertEqual(friend_request.sender, self.sender)
|
||||||
|
self.assertEqual(friend_request.receiver, self.receiver)
|
||||||
|
|
||||||
|
def test_unique_together_constraint(self):
|
||||||
|
"""Test that the unique_together constraint works"""
|
||||||
|
FriendRequest.objects.create(
|
||||||
|
sender=self.sender,
|
||||||
|
receiver=self.receiver
|
||||||
|
)
|
||||||
|
|
||||||
|
# Creating another request with the same sender and receiver should raise an error
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
FriendRequest.objects.create(
|
||||||
|
sender=self.sender,
|
||||||
|
receiver=self.receiver
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class InvitationModelTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.creator = User.objects.create_user(
|
||||||
|
username='creator',
|
||||||
|
email='creator@example.com',
|
||||||
|
password='creatorpassword'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_invitation_creation(self):
|
||||||
|
"""Test creating an invitation"""
|
||||||
|
invitation = Invitation.objects.create(
|
||||||
|
created_by=self.creator
|
||||||
|
)
|
||||||
|
self.assertEqual(invitation.created_by, self.creator)
|
||||||
|
self.assertIsNotNone(invitation.token)
|
||||||
|
self.assertIsNone(invitation.user)
|
||||||
|
|
||||||
|
def test_invitation_assignment(self):
|
||||||
|
"""Test assigning an invitation to a user"""
|
||||||
|
invitation = Invitation.objects.create(
|
||||||
|
created_by=self.creator
|
||||||
|
)
|
||||||
|
|
||||||
|
new_user = User.objects.create_user(
|
||||||
|
username='newuser',
|
||||||
|
email='new@example.com',
|
||||||
|
password='newpassword'
|
||||||
|
)
|
||||||
|
|
||||||
|
invitation.user = new_user
|
||||||
|
invitation.save()
|
||||||
|
|
||||||
|
# Refresh from database
|
||||||
|
invitation.refresh_from_db()
|
||||||
|
self.assertEqual(invitation.user, new_user)
|
||||||
|
|
||||||
|
|
||||||
|
class UserViewSetTestCase(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.user = User.objects.create_user(
|
||||||
|
username='testuser',
|
||||||
|
email='test@example.com',
|
||||||
|
password='testpassword'
|
||||||
|
)
|
||||||
|
self.friend = User.objects.create_user(
|
||||||
|
username='frienduser',
|
||||||
|
email='friend@example.com',
|
||||||
|
password='friendpassword'
|
||||||
|
)
|
||||||
|
self.client = APIClient()
|
||||||
|
self.client.force_authenticate(user=self.user)
|
||||||
|
|
||||||
|
def test_list_users(self):
|
||||||
|
"""Test listing users"""
|
||||||
|
url = reverse('user-list')
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(len(response.data), 2) # Should include both users
|
||||||
|
|
||||||
|
def test_retrieve_user(self):
|
||||||
|
"""Test retrieving a specific user"""
|
||||||
|
url = reverse('user-detail', args=[self.friend.id])
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(response.data['username'], 'frienduser')
|
||||||
|
|
||||||
|
def test_add_friend_request(self):
|
||||||
|
"""Test adding a friend request"""
|
||||||
|
url = reverse('user-add-friend-request', args=[self.friend.username])
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertTrue(response.data['success'])
|
||||||
|
self.assertEqual(response.data['message'], 'Request sent')
|
||||||
|
|
||||||
|
# Verify the friend request was created
|
||||||
|
self.assertTrue(FriendRequest.objects.filter(
|
||||||
|
sender=self.user,
|
||||||
|
receiver=self.friend
|
||||||
|
).exists())
|
||||||
|
|
||||||
|
def test_add_friend_request_nonexistent_user(self):
|
||||||
|
"""Test adding a friend request to a nonexistent user"""
|
||||||
|
url = reverse('user-add-friend-request', args=['nonexistentuser'])
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertFalse(response.data['success'])
|
||||||
|
self.assertEqual(response.data['message'], "User 'nonexistentuser' doesn't exist")
|
||||||
|
|
||||||
|
def test_remove_friend(self):
|
||||||
|
"""Test removing a friend"""
|
||||||
|
# First add as friend
|
||||||
|
self.user.friends.add(self.friend)
|
||||||
|
|
||||||
|
url = reverse('user-remove-friend', args=[self.friend.id])
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertTrue(response.data['success'])
|
||||||
|
|
||||||
|
# Verify the friend was removed
|
||||||
|
self.assertFalse(self.user.friends.filter(id=self.friend.id).exists())
|
||||||
|
|
||||||
|
@patch('user.views.shutil.disk_usage')
|
||||||
|
def test_user_stats(self, mock_disk_usage):
|
||||||
|
"""Test getting user stats"""
|
||||||
|
# Mock disk_usage return value
|
||||||
|
mock_disk_usage.return_value = MagicMock(
|
||||||
|
total=1000000,
|
||||||
|
used=500000,
|
||||||
|
free=500000
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create torrents for the user
|
||||||
|
Torrent.objects.create(
|
||||||
|
id='abc123',
|
||||||
|
name='Test Torrent',
|
||||||
|
user=self.user,
|
||||||
|
size=5000,
|
||||||
|
transmission_data={}
|
||||||
|
)
|
||||||
|
|
||||||
|
url = reverse('user-user-stats')
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
# Check that the response contains the expected fields
|
||||||
|
self.assertIn('torrents_size', response.data)
|
||||||
|
self.assertIn('torrents_len', response.data)
|
||||||
|
self.assertIn('user_max_size', response.data)
|
||||||
|
self.assertIn('disk_total', response.data)
|
||||||
|
self.assertIn('disk_used', response.data)
|
||||||
|
self.assertIn('disk_free', response.data)
|
||||||
|
|
||||||
|
|
||||||
|
class FriendRequestViewSetTestCase(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.user = User.objects.create_user(
|
||||||
|
username='testuser',
|
||||||
|
email='test@example.com',
|
||||||
|
password='testpassword'
|
||||||
|
)
|
||||||
|
self.sender = User.objects.create_user(
|
||||||
|
username='sender',
|
||||||
|
email='sender@example.com',
|
||||||
|
password='senderpassword'
|
||||||
|
)
|
||||||
|
self.client = APIClient()
|
||||||
|
self.client.force_authenticate(user=self.user)
|
||||||
|
|
||||||
|
# Create a friend request
|
||||||
|
self.friend_request = FriendRequest.objects.create(
|
||||||
|
sender=self.sender,
|
||||||
|
receiver=self.user
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_list_friend_requests(self):
|
||||||
|
"""Test listing friend requests"""
|
||||||
|
url = reverse('friendrequest-list')
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(len(response.data), 1)
|
||||||
|
self.assertEqual(response.data[0]['sender']['username'], 'sender')
|
||||||
|
|
||||||
|
def test_delete_friend_request(self):
|
||||||
|
"""Test deleting a friend request"""
|
||||||
|
url = reverse('friendrequest-detail', args=[self.friend_request.id])
|
||||||
|
response = self.client.delete(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
# Verify the friend request was deleted
|
||||||
|
self.assertFalse(FriendRequest.objects.filter(id=self.friend_request.id).exists())
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class UserViewSet(mixins.RetrieveModelMixin,
|
|||||||
stats = User.objects.filter(id=request.user.id).aggregate(
|
stats = User.objects.filter(id=request.user.id).aggregate(
|
||||||
total_size=Sum("torrents__size"),
|
total_size=Sum("torrents__size"),
|
||||||
total_torrent=Count("torrents"),
|
total_torrent=Count("torrents"),
|
||||||
total_shared_torrent=Count("torrents_shares")
|
total_shared_torrent=Count("torrents_shares", distinct=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
disk_usage = shutil.disk_usage("/")
|
disk_usage = shutil.disk_usage("/")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
redis:
|
redis:
|
||||||
image: redis:alpine
|
image: valkey/valkey:alpine
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
web:
|
web:
|
||||||
|
|||||||
Reference in New Issue
Block a user