Adding more tests and fixes

This commit is contained in:
Adolfo Gómez García 2022-08-24 22:16:47 +02:00
parent 5f12c2e7b3
commit 69b778c922
22 changed files with 342 additions and 51 deletions

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2020 Virtual Cable S.L.
# Copyright (c) 2020-2022 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -35,7 +35,7 @@ import os
import logging
import typing
import PyQt5 # pylint: disable=unused-import
import PyQt5 # Ensures PyQt is included in the package
from PyQt5.QtWidgets import QApplication, QDialog, QFileDialog, QMessageBox
import udsactor

View File

@ -326,6 +326,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
if self.isManaged()
else (initResult.alias_token or self._cfg.master_token)
)
# Replace master token with alias token if present
self._cfg = self._cfg._replace(
master_token=master_token,
own_token=initResult.own_token,

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2022 Virtual Cable S.L.U.
@ -29,6 +28,5 @@
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
from . import test_login_logout
from . import test_register
from . import test_initialize
from . import actor

View File

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2022 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
from . import test_login_logout
from . import test_register
from . import test_initialize

View File

@ -37,9 +37,8 @@ from django.conf import settings
from uds import models
from uds.core import VERSION
from uds.REST.handlers import AUTH_TOKEN_HEADER
from uds.models import user_service
from ..utils import rest, constants
from ...utils import rest
logger = logging.getLogger(__name__)
@ -153,35 +152,42 @@ class ActorInitializeV3(rest.test.RESTTestCase):
success = functools.partial(self.invoke_success, 'unmanaged')
failure = functools.partial(self.invoke_failure, 'unmanaged')
# This will succeed, but only alias token is provided
# This will succeed, but only alias token is returned because MAC is not registered by UDS
result = success(
actor_token,
mac='00:00:00:00:00:00',
)
alias_token = result['alias_token']
# Unmanaged host is the response for initialization of unmanaged actor ALWAYS
self.assertIsNone(result['alias_token'])
self.assertIsNone(result['own_token'])
self.assertIsInstance(alias_token, str)
self.assertIsNone(result['unique_id'])
self.assertIsNone(result['os'])
# Now, invoke a "nice" initialize
result = success(
actor_token,
mac=unique_id,
)
alias_token = result['alias_token']
self.assertIsInstance(alias_token, str)
self.assertEqual(result['own_token'], user_service.uuid)
self.assertEqual(result['alias_token'], alias_token)
self.assertEqual(result['unique_id'], unique_id)
# Ensure that the alias returned is on alias db, and it points to the same service as the one we belong to
alias = models.ServiceTokenAlias.objects.get(alias=result['alias_token'])
self.assertEqual(alias.service, user_service.deployed_service.service)
# Now, we should be able to "initialize" with valid mac and with original and alias tokens
# If we call initialize and we get "own-token" means that we have already logged in with this data
result = success(
alias_token,
mac=unique_id
)
result = success(alias_token, mac=unique_id)
self.assertEqual(result['own_token'], user_service.uuid)
self.assertEqual(result['alias_token'], alias_token)
self.assertEqual(result['unique_id'], unique_id)
logger.info('Result: %s', result)
#
failure('invalid token', unique_id, True)

View File

@ -32,8 +32,8 @@ import random
import typing
from .. import fixtures
from ..utils import rest, test
from ... import fixtures
from ...utils import rest, test
class RESTLoginLogoutCase(test.UDSTestCase):
@ -41,7 +41,7 @@ class RESTLoginLogoutCase(test.UDSTestCase):
Test login and logout
"""
def test_login_logout(self):
def test_login_logout(self) -> None:
"""
Test login and logout
"""

View File

@ -31,12 +31,10 @@
import typing
import logging
from django.conf import settings
from uds import models
from uds.REST.handlers import AUTH_TOKEN_HEADER
from ..utils import rest, constants
from ...utils import rest, constants
logger = logging.getLogger(__name__)

View File

@ -34,7 +34,7 @@ import logging
from uds.REST.handlers import AUTH_TOKEN_HEADER
from uds.REST.methods.actor_v3 import MANAGED, UNMANAGED, ALLOWED_FAILS
from ..utils import rest
from ...utils import rest
logger = logging.getLogger(__name__)

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2022 Virtual Cable S.L.
# Copyright (c) 2022 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -11,7 +11,7 @@
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
@ -28,5 +28,5 @@
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
from . import test_cache
from . import test_stats_counters
from . import util
from . import managers

View File

@ -0,0 +1 @@
This file is the downloadable for download manager tests

View File

@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2022 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import typing
import os.path
import sys
# We use commit/rollback
from ...utils.test import UDSTransactionTestCase
from django.urls import reverse
from uds.core.managers.downloads import DownloadsManager
from uds.core.util.config import GlobalConfig
if typing.TYPE_CHECKING:
from django.http import HttpResponse
class DownloadsManagerTests(UDSTransactionTestCase):
filePath: str = ''
manager: DownloadsManager
@classmethod
def setUpClass(cls):
from uds.core.managers import downloadsManager
super(DownloadsManagerTests, cls).setUpClass()
cls.filePath = os.path.join(
os.path.dirname(os.path.abspath(__file__)), 'downloadable.txt'
)
cls.manager = downloadsManager()
def test_downloads(self):
for v in (
('test.txt', 'text/plain', '1f47ec0a-1ad4-5d63-b41c-5d2befadab8d'),
(
'test.bin',
'application/octet-stream',
'6454d619-cc62-5bd4-aa9a-c9e2458d44da',
),
):
fileName, mimeType, knownUuid = v
self.manager.registerDownloadable(
fileName,
'This is the test file {}'.format(fileName),
self.filePath,
mimeType,
)
downloadables = self.manager.getDownloadables()
self.assertIn(
knownUuid,
downloadables,
'The File {} was not found in downloadables!'.format(fileName),
)
# This will fail, no user has logged in
self.client.get(
reverse('utility.downloader', kwargs={'idDownload': knownUuid})
)
# Remove last '/' for redirect check. URL redirection will not contain it
# Commented because i don't know why when executed in batch returns the last '/', and alone don't
# self.assertRedirects(response, reverse('uds.web.views.login'), fetch_redirect_response=False)
# And try to download again
response = self.client.get(
reverse('utility.downloader', kwargs={'idDownload': knownUuid})
)
self.assertEqual(
response.get('Content-Type'),
mimeType,
'Mime type of {} is not {} as expected (it is {})'.format(
fileName, mimeType, response.get('Content-Type')
),
)
self.assertContains(
response,
'This file is the downloadable for download manager tests',
msg_prefix='File does not seems to be fine',
)
# Now do logout
# response = client.get(reverse('uds.web.views.logout'))
# self.assertRedirects(response, reverse('uds.web.views.login')[:-1], fetch_redirect_response=False)

View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2022 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
from . import test_cache
from . import test_stats_counters

View File

@ -32,7 +32,7 @@
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
# We use commit/rollback
from django.test import TransactionTestCase as TestCase
from ...utils.test import UDSTransactionTestCase
from uds.core.util.cache import Cache
import time
@ -41,7 +41,7 @@ UNICODE_CHARS_2 = 'ñöçóá^(€íöè)'
VALUE_1 = [u'únîcödè€', b'string', {'a': 1, 'b': 2.0}]
class CacheTests(TestCase):
class CacheTests(UDSTransactionTestCase):
def test_cache(self):
cache = Cache(UNICODE_CHARS)
@ -60,7 +60,7 @@ class CacheTests(TestCase):
# Add new "str" key, with 1 second life, wait 2 seconds and recover
cache.put(b'key', VALUE_1, 1)
time.sleep(2)
time.sleep(1.1)
self.assertEqual(cache.get(b'key'), None, 'Put an "str" key and recover it once it has expired')
# Refresh previous key and will be again available
@ -79,7 +79,7 @@ class CacheTests(TestCase):
# Checks cache cleanup (remove expired keys)
cache.put('key', 'test', 0)
time.sleep(1)
time.sleep(0.1)
Cache.cleanUp()
cache.refresh('key')
self.assertEqual(cache.get('key'), None, 'Put a key and recover it once it has expired and has been cleaned')

View File

@ -34,10 +34,10 @@
import datetime
import typing
from ..fixtures.stats_counters import create_stats_counters
from ...fixtures.stats_counters import create_stats_counters
# We use commit/rollback
from django.test import TestCase
from ...utils.test import UDSTransactionTestCase
from uds.core.util.stats import counters
from uds import models
@ -47,7 +47,7 @@ END_DATE_DAY = datetime.datetime(2020, 1, 2, 0, 0, 0)
END_DATE_MONTH = datetime.datetime(2020, 2, 1, 0, 0, 0)
END_DATE_YEAR = datetime.datetime(2021, 1, 1, 0, 0, 0)
class StatsCountersTests(TestCase):
class StatsCountersTests(UDSTransactionTestCase):
def setUp(self) -> None:
return super().setUp()

93
server/src/tests/fixtures/calendars.py vendored Normal file
View File

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2022 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import typing
import datetime
import random
from uds import models
from uds.models.calendar_rule import freqs, dunits
# Counters so we can reinvoke the same method and generate new data
glob = {
'calendar_id': 1,
'calendar_rule_id': 1,
}
def createCalendars(number: int) -> typing.List[models.Calendar]:
"""
Creates some testing calendars
"""
calendars: typing.List[models.Calendar] = []
for i in range(number):
calendar = models.Calendar.objects.create(
name='Calendar {}'.format(glob['calendar_id']),
comments='Calendar {} comments'.format(glob['calendar_id']),
)
calendars.append(calendar)
glob['calendar_id'] += 1
return calendars
def createRules(number: int, calendar: models.Calendar) -> typing.List[models.CalendarRule]:
"""
Creates some testing rules associated to a calendar
"""
rules: typing.List[models.CalendarRule] = []
rnd = random.Random() # nosec: testing purposes
for i in range(number):
# All rules will start now
# Rules duration will be a random between 1 and 10 days
# freqs a random value from freqs
# interval is a random value between 1 and 10
# duration is a random value between 0 and 24
# duration_unit is a random from dunits
start = datetime.datetime.now()
end = start + datetime.timedelta(days=rnd.randint(1, 10))
freq = rnd.choice(freqs)
interval = rnd.randint(1, 10)
duration = rnd.randint(0, 24)
duration_unit = rnd.choice(dunits)
rule = models.CalendarRule.objects.create(
calendar=calendar,
name='Rule {}'.format(glob['calendar_rule_id']),
comments='Rule {} comments'.format(glob['calendar_rule_id']),
start=start,
end=end,
freq=freq,
interval=interval,
duration=duration,
duration_unit=duration_unit,
)
rules.append(rule)
glob['calendar_rule_id'] += 1
return rules

View File

@ -42,7 +42,7 @@ from uds.REST.handlers import AUTH_TOKEN_HEADER
NUMBER_OF_ITEMS_TO_CREATE = 4
class RESTTestCase(test.UDSTransactionTestCasse):
class RESTTestCase(test.UDSTransactionTestCase):
# Authenticators related
auth: models.Authenticator
groups: typing.List[models.Group]

View File

@ -33,6 +33,7 @@ import logging
from django.test import TestCase, TransactionTestCase
from django.test.client import Client
from django.http import HttpResponse
from django.conf import settings
from uds import models
@ -76,12 +77,19 @@ class UDSClient(Client):
return super().request(**request)
def get(self, *args, **kwargs) -> 'HttpResponse':
return typing.cast('HttpResponse', super().get(*args, **kwargs))
def post(self, *args, **kwargs) -> 'HttpResponse':
return typing.cast('HttpResponse', super().post(*args, **kwargs))
class UDSTestCase(TestCase):
client_class: typing.Type = UDSClient
client: UDSClient
class UDSTransactionTestCasse(TransactionTestCase):
class UDSTransactionTestCase(TransactionTestCase):
client_class: typing.Type = UDSClient
client: UDSClient

View File

@ -31,6 +31,8 @@
import random
import typing
from django.urls import reverse
from uds.core.util.config import GlobalConfig
from ...utils import test
@ -43,7 +45,7 @@ from uds import models
class WebLoginLogout(test.UDSTransactionTestCasse):
class WebLoginLogout(test.UDSTransactionTestCase):
"""
Test WEB login and logout
"""
@ -105,7 +107,7 @@ class WebLoginLogout(test.UDSTransactionTestCasse):
# Now invoke logout
response = typing.cast('HttpResponse', self.client.get('/uds/page/logout'))
self.assertRedirects(
response, 'http://testserver/uds/page/login', status_code=302
response, reverse('page.login'), status_code=302
)
# Ensures a couple of logs are created for every operation
# Except for root, that has no user associated on db

View File

@ -302,20 +302,16 @@ class Initialize(ActorV3Action):
token = self._params['token']
# First, try to locate an user service providing this token.
if self._params['type'] == UNMANAGED:
alias_token = token # Store token as possible alias
# First, try to locate on alias table
if ServiceTokenAlias.objects.filter(alias=token).exists():
# Retrieve real service from token alias
service = ServiceTokenAlias.objects.get(alias=token).service
alias_token = token # Store token as possible alias
# If not found an alias, try to locate on service table
# Not on alias token, try to locate on Service table
if not service:
service = typing.cast('Service', Service.objects.get(token=token))
# And create a new alias for it, and save
alias_token = (
cryptoManager().randomString(40)
) # fix alias with new token
service.aliases.create(alias=alias_token)
# Locate an userService that belongs to this service and which
# Build the possible ids and make initial filter to match service
@ -355,6 +351,14 @@ class Initialize(ActorV3Action):
if osManager:
osData = osManager.actorData(userService)
if service and not alias_token: # Is a service managed by UDS
# Create a new alias for it, and save
alias_token = (
cryptoManager().randomString(40)
) # fix alias with new token
service.aliases.create(alias=alias_token)
return initialization_result(
userService.uuid, userService.unique_id, osData, alias_token
)

View File

@ -454,7 +454,7 @@ def webLogout(
username = request.user.name
logout = authenticator.logout(request, username)
if logout and logout.success == auths.AuthenticationSuccess.REDIRECT:
exit_url = logout.url
exit_url = logout.url or exit_url
if request.user.id != ROOT_ID:
# Log the event if not root user
events.addEvent(
@ -472,7 +472,7 @@ def webLogout(
request.session.flush()
request.authorized = False
response = HttpResponseRedirect(exit_url) # type: ignore
response = HttpResponseRedirect(exit_url)
if authenticator:
authenticator.webLogoutHook(username, request, response)
return response

View File

@ -109,12 +109,12 @@ class CalendarRule(UUIDModel):
duration = models.IntegerField(default=0) # Duration in minutes
duration_unit = models.CharField(choices=dunits, default='MINUTES', max_length=32)
calendar: 'models.ForeignKey[CalendarRule, Calendar]' = models.ForeignKey(
calendar: 'models.ForeignKey["CalendarRule", Calendar]' = models.ForeignKey(
Calendar, related_name='rules', on_delete=models.CASCADE
)
# "fake" declarations for type checking
objects: 'models.manager.Manager[CalendarRule]'
objects: 'models.manager.Manager["CalendarRule"]'
class Meta:
"""