Skip to content
Open

Lab6 #118

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.
142 changes: 142 additions & 0 deletions reports/Melnik/lab6/src/test_all.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import pytest
import requests
from unittest.mock import patch

# ========== КЛАСС CART ==========
class Cart:
def __init__(self):
self.items = []

def add_item(self, item: str, price: float):
if price < 0:
raise ValueError("Цена не может быть отрицательной")
self.items.append((item, price))

def total(self) -> float:
return sum(price for _, price in self.items)

def apply_discount(self, discount_percent: float):
if discount_percent < 0 or discount_percent > 100:
raise ValueError("Скидка должна быть от 0 до 100 процентов")
multiplier = 1 - (discount_percent / 100)
self.items = [(item, price * multiplier) for item, price in self.items]

def apply_coupon(self, coupon_code: str):
coupons = {"SAVE10": 10, "HALF": 50}
if coupon_code in coupons:
self.apply_discount(coupons[coupon_code])
else:
raise ValueError("Invalid coupon")


def log_purchase(item):
requests.post("https://example.com/log", json=item)


# ========== ФУНКЦИЯ STRING REPEAT ==========
def string_repeat(pattern: str, repeat: int) -> str:
if pattern is None:
raise TypeError("pattern не может быть None")
if repeat < 0:
raise ValueError("repeat не может быть отрицательным")
return pattern * repeat


# ========== ТЕСТЫ ==========
@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] == ("Apple", 10.0)


def test_add_item_negative_price(empty_cart):
with pytest.raises(ValueError, match="Цена не может быть отрицательной"):
empty_cart.add_item("Orange", -5.0)


def test_total(empty_cart):
empty_cart.add_item("Apple", 10.0)
empty_cart.add_item("Banana", 20.0)
assert empty_cart.total() == 30.0


@pytest.mark.parametrize("discount, expected_total", [
(0, 30.0),
(50, 15.0),
(100, 0.0),
])
def test_apply_discount_valid(empty_cart, discount, expected_total):
empty_cart.add_item("Apple", 10.0)
empty_cart.add_item("Banana", 20.0)
empty_cart.apply_discount(discount)
assert empty_cart.total() == expected_total


@pytest.mark.parametrize("discount", [-10, 150, 200])
def test_apply_discount_invalid(empty_cart, discount):
empty_cart.add_item("Apple", 10.0)
with pytest.raises(ValueError, match="Скидка должна быть от 0 до 100 процентов"):
empty_cart.apply_discount(discount)


def test_apply_coupon_valid(empty_cart):
empty_cart.add_item("Apple", 100.0)
empty_cart.apply_coupon("SAVE10")
assert empty_cart.total() == 90.0


def test_apply_coupon_invalid(empty_cart):
empty_cart.add_item("Apple", 100.0)
with pytest.raises(ValueError, match="Invalid coupon"):
empty_cart.apply_coupon("INVALID")


def test_log_purchase_mock():
with patch("requests.post") as mock_post:
log_purchase({"item": "Apple", "price": 10.0})
mock_post.assert_called_once_with(
"https://example.com/log",
json={"item": "Apple", "price": 10.0}
)


def test_string_repeat_zero():
assert string_repeat("e", 0) == ""


def test_string_repeat_positive():
assert string_repeat("e", 3) == "eee"


def test_string_repeat_with_spaces():
assert string_repeat(" ABC ", 2) == " ABC ABC "


def test_string_repeat_negative():
with pytest.raises(ValueError, match="repeat не может быть отрицательным"):
string_repeat("e", -2)


def test_string_repeat_none():
with pytest.raises(TypeError, match="pattern не может быть None"):
string_repeat(None, 1)


@pytest.mark.parametrize("pattern, repeat, expected", [
("a", 5, "aaaaa"),
("abc", 3, "abcabcabc"),
("", 10, ""),
("x", 1, "x"),
])
def test_string_repeat_parametrized(pattern, repeat, expected):
assert string_repeat(pattern, repeat) == expected


if __name__ == "__main__":
pytest.main(["-v"])

Loading