Skip to content
Open

Lab6 #140

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added reports/Melnik/lab4/rep/rep.pdf
Binary file not shown.
Empty file.
87 changes: 87 additions & 0 deletions reports/Melnik/lab4/src/task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# pylint: disable=invalid-name
"""
Скрипт для автоматического отслеживания новых релизов в репозиториях GitHub.
Использует GitHub REST API и сохраняет состояние в JSON файл.
"""

import json
import os
import requests

STATE_FILE = "last_releases.json"


def get_latest_release(repo: str) -> dict:
"""
Получает информацию о последнем релизе репозитория через GitHub API.
"""
url = f"https://api.github.com/repos/{repo}/releases/latest"
headers = {"Accept": "application/vnd.github.v3+json"}

try:
response = requests.get(url, headers=headers, timeout=10)
if response.status_code == 200:
return response.json()
return None
except requests.exceptions.RequestException as err:
print(f"Ошибка сети для {repo}: {err}")
return None


def load_state() -> dict:
"""Загружает данные о прошлых проверках из JSON файла."""
if os.path.exists(STATE_FILE):
with open(STATE_FILE, "r", encoding="utf-8") as f:
return json.load(f)
return {}


def save_state(state: dict) -> None:
"""Сохраняет текущие данные о версиях в JSON файл."""
with open(STATE_FILE, "w", encoding="utf-8") as f:
json.dump(state, f, ensure_ascii=False, indent=4)


def main() -> None:
"""Основная логика мониторинга обновлений."""
user_input = input("Введите репозитории для отслеживания (через запятую): ")
repo_list = [r.strip() for r in user_input.split(",") if r.strip()]

if not repo_list:
print("Список репозиториев пуст.")
return

last_state = load_state()
new_state = last_state.copy()

for repo in repo_list:
print(f"\nПроверяем обновления для {repo}...")
release_data = get_latest_release(repo)

if not release_data:
print(f"Информация о релизах в {repo} не найдена.")
continue

version = release_data.get("tag_name")
last_seen_version = last_state.get(repo)

if version != last_seen_version:
date = release_data.get("published_at", "не указана")[:10]
link = release_data.get("html_url")
body = release_data.get("body", "Нет описания")
changelog = "\n".join(body.splitlines()[:5])

print(f"✅ НАЙДЕН НОВЫЙ РЕЛИЗ: {version} ({date})")
print(f" Ссылка: {link}")
print(f" Основные изменения:\n{changelog}...")

new_state[repo] = version
else:
print(f"😴 Новых обновлений для {repo} нет (текущая: {version}).")

save_state(new_state)
print("\nПроверка завершена. Состояние сохранено в last_releases.json")


if __name__ == "__main__":
main()
Binary file added reports/Melnik/lab6/rep/rep6.pdf
Binary file not shown.
Empty file added reports/Melnik/lab6/src/lab.py
Empty file.
48 changes: 48 additions & 0 deletions reports/Melnik/lab6/src/shopping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""
Мини-библиотека покупок.
Обеспечивает работу с корзиной, товарами, скидками и купонами.
"""

import requests

coupons = {"SAVE10": 10, "HALF": 50}


class Cart:
"""Класс, представляющий корзину покупок клиента."""

def __init__(self):
"""Инициализация пустой корзины."""
self.items = []

def add_item(self, name: str, price: float) -> None:
"""Добавляет новый товар в корзину. Выбрасывает ошибку при отрицательной цене."""
if price < 0:
raise ValueError("Цена не может быть отрицательной")
self.items.append({"name": name, "price": price})

def total(self) -> float:
"""Вычисляет общую стоимость всех товаров в корзине."""
return sum(item["price"] for item in self.items)

def apply_discount(self, discount: float) -> None:
"""Применяет процентную скидку ко всем товарам в корзине."""
if discount < 0 or discount > 100:
raise ValueError("Скидка должна быть от 0 до 100 процентов")

factor = (100 - discount) / 100
for item in self.items:
item["price"] *= factor


def log_purchase(item: dict) -> None:
"""Отправляет лог с информацией о покупке на удаленный сервер."""
requests.post("https://example.com/log", json=item, timeout=10)


def apply_coupon(cart: Cart, coupon_code: str) -> None:
"""Применяет скидку к корзине на основе переданного промокода."""
if coupon_code in coupons:
cart.apply_discount(coupons[coupon_code])
else:
raise ValueError("Invalid coupon")
54 changes: 54 additions & 0 deletions reports/Melnik/lab6/src/task3_indexOfDifference.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# pylint: disable=invalid-name
"""
Модуль содержит реализацию функции indexOfDifference и тесты к ней.
Задание 3, вариант 5.
"""

import pytest


def indexOfDifference(str1: str, str2: str) -> int:
"""
Возвращает индекс первой позиции, в которой строки различаются.
Если строки идентичны, возвращает -1.
Если передан None, выбрасывает TypeError.
"""
if str1 is None or str2 is None:
raise TypeError("Строки не могут быть None")

if str1 == str2:
return -1

min_len = min(len(str1), len(str2))
for i in range(min_len):
if str1[i] != str2[i]:
return i

return min_len


@pytest.mark.parametrize(
"str1, str2, expected_index",
[
("", "", -1),
("", "abc", 0),
("abc", "", 0),
("abc", "abc", -1),
("ab", "abxyz", 2),
("abcde", "abxyz", 2),
("abcde", "xyz", 0),
("i am a machine", "i am a robot", 7),
],
)
def test_index_of_difference_logic(str1, str2, expected_index):
"""Проверка функции indexOfDifference на корректных и граничных значениях."""
assert indexOfDifference(str1, str2) == expected_index


def test_index_of_difference_type_error():
"""Проверка выброса TypeError при передаче None в качестве аргумента."""
with pytest.raises(TypeError):
indexOfDifference(None, None)

with pytest.raises(TypeError):
indexOfDifference("abc", None)
98 changes: 98 additions & 0 deletions reports/Melnik/lab6/src/test_cart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# pylint: disable=invalid-name, redefined-outer-name, too-few-public-methods
"""
Модуль для тестирования мини-библиотеки покупок (shopping.py).
Включает тесты добавления товаров, применения скидок, купонов и логгирования.
"""

import pytest
import requests
import shopping
from shopping import Cart, log_purchase, apply_coupon


@pytest.fixture
def empty_cart():
"""Фикстура для создания пустой корзины перед каждым тестом."""
return Cart()


def test_add_item(empty_cart):
"""Проверка успешного добавления товара в корзину."""
empty_cart.add_item("Apple", 10.0)
assert len(empty_cart.items) == 1
assert empty_cart.items[0]["name"] == "Apple"
assert empty_cart.items[0]["price"] == 10.0


def test_negative_price(empty_cart):
"""Проверка выброса исключения при добавлении товара с отрицательной ценой."""
with pytest.raises(ValueError, match="Цена не может быть отрицательной"):
empty_cart.add_item("Banana", -5.0)


def test_total(empty_cart):
"""Проверка корректного вычисления общей стоимости товаров в корзине."""
empty_cart.add_item("Apple", 10.0)
empty_cart.add_item("Milk", 15.0)
assert empty_cart.total() == 25.0


@pytest.mark.parametrize(
"discount, expected_total", [(0, 100.0), (50, 50.0), (100, 0.0)]
)
def test_apply_discount_valid(empty_cart, discount, expected_total):
"""Проверка корректного применения валидных скидок (0, 50, 100 процентов)."""
empty_cart.add_item("Jacket", 100.0)
empty_cart.apply_discount(discount)
assert empty_cart.total() == expected_total


@pytest.mark.parametrize("invalid_discount", [-10, 150])
def test_apply_discount_invalid(empty_cart, invalid_discount):
"""Проверка выброса исключения при недопустимых значениях скидки."""
empty_cart.add_item("Jacket", 100.0)
with pytest.raises(ValueError):
empty_cart.apply_discount(invalid_discount)


def test_log_purchase(monkeypatch):
"""Тестирование функции логгирования покупок с использованием мокирования."""
mock_data = {}

def mock_post(url, json, timeout=10):
"""Заглушка для имитации POST-запроса."""
mock_data["url"] = url
mock_data["json"] = json
mock_data["timeout"] = timeout

class MockResponse:
"""Фейковый ответ сервера."""

status_code = 200

return MockResponse()

monkeypatch.setattr(requests, "post", mock_post)

item = {"name": "Laptop", "price": 1000.0}
log_purchase(item)

assert mock_data["url"] == "https://example.com/log"
assert mock_data["json"] == item
assert mock_data["timeout"] == 10


def test_apply_coupon_valid(empty_cart, monkeypatch):
"""Проверка применения валидного купона с подменой словаря купонов."""
monkeypatch.setattr(shopping, "coupons", {"TEST20": 20})

empty_cart.add_item("Shoes", 100.0)
apply_coupon(empty_cart, "TEST20")

assert empty_cart.total() == 80.0


def test_apply_coupon_invalid(empty_cart):
"""Проверка выброса исключения при использовании несуществующего купона."""
with pytest.raises(ValueError, match="Invalid coupon"):
apply_coupon(empty_cart, "WRONG_CODE")
50 changes: 50 additions & 0 deletions reports/Melnik/lab6/src/test_lab2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""
lab2_tests
"""

import pytest
from lab2 import BoundedIntSet


def test_create_bounded_set():
"""Тривиальный случай: создание множества и проверка базовых атрибутов."""
s = BoundedIntSet(capacity=5, initial_elements=[1, 2, 3])
assert s.capacity == 5
assert s.elements == [1, 2, 3]


def test_negative_capacity():
"""Исключительная ситуация: отрицательная мощность."""
with pytest.raises(ValueError):
BoundedIntSet(capacity=-1)


def test_add_duplicate_element():
"""Граничный случай: добавление дубликата игнорируется."""
s = BoundedIntSet(capacity=3, initial_elements=[1])
s.add(1)
assert len(s.elements) == 1


def test_add_overflow():
"""Исключительная ситуация: превышение мощности (OverflowError)."""
s = BoundedIntSet(capacity=2, initial_elements=[1, 2])
with pytest.raises(OverflowError):
s.add(3)


def test_remove_missing_element():
"""Исключительная ситуация: удаление несуществующего элемента."""
s = BoundedIntSet(capacity=5, initial_elements=[1, 2])
with pytest.raises(ValueError):
s.remove(99)


def test_set_union():
"""Тривиальный случай: объединение множеств."""
set1 = BoundedIntSet(capacity=2, initial_elements=[1, 2])
set2 = BoundedIntSet(capacity=2, initial_elements=[2, 3])

union_set = set1.union(set2)
assert union_set.capacity == 4
assert sorted(union_set.elements) == [1, 2, 3]
Loading