Skip to content

Commit 66ca1d3

Browse files
refactor app_gui.py for mainloop. Added tests and CI
1 parent 6470aed commit 66ca1d3

File tree

13 files changed

+190
-137
lines changed

13 files changed

+190
-137
lines changed

.github/workflows/build_binary.yml

Lines changed: 0 additions & 74 deletions
This file was deleted.

.github/workflows/ci.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
name: CI
3+
on: [push, pull_request]
4+
5+
permissions:
6+
contents: write
7+
8+
jobs:
9+
lint-test:
10+
runs-on: ubuntu-24.04
11+
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: actions/setup-python@v5
16+
with:
17+
python-version: '3.11'
18+
19+
- name: Cache pip
20+
uses: actions/cache@v4
21+
with:
22+
path: ~/.cache/pip
23+
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
24+
25+
- name: Install system GUI deps
26+
run: |
27+
sudo apt-get update
28+
sudo apt-get install -y python3-tk xvfb
29+
30+
- name: Install deps
31+
run: |
32+
python -m pip install --upgrade pip
33+
pip install -r requirements.txt
34+
pip install isort
35+
36+
- name: Auto-format (black + isort)
37+
run: |
38+
isort .
39+
black .
40+
41+
- name: Auto-commit formatted files
42+
uses: stefanzweifel/git-auto-commit-action@v5
43+
with:
44+
commit_message: "ci(fmt): black+isort [skip ci]"
45+
file_pattern: "**/*.py"
46+
47+
- name: Run tests (headless Tk)
48+
env:
49+
SERPAPI_KEY: "test-key"
50+
run: xvfb-run -a pytest -q -m "not slow"

.github/workflows/license.yml

Lines changed: 0 additions & 32 deletions
This file was deleted.

app_gui.py

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -135,31 +135,23 @@ def choose_dir():
135135

136136

137137
# --- UI ---
138-
root = tk.Tk()
139-
root.title("GinioCrawler")
140-
root.geometry("560x220")
141-
142-
# fraza
143-
tk.Label(root, text="Fraza do wyszukania:").pack(anchor="w", padx=12, pady=(12, 0))
144-
entry_query = tk.Entry(root)
145-
entry_query.pack(fill="x", padx=12, pady=6)
146-
entry_query.focus()
147-
148-
# folder wyjściowy
149-
frm = tk.Frame(root)
150-
frm.pack(fill="x", padx=12, pady=(0, 6))
151-
tk.Label(frm, text="Folder wyjściowy:").pack(side="left")
152-
out_dir_var = tk.StringVar(value=str((Path.cwd() / "wyniki")))
153-
entry_dir = tk.Entry(frm, textvariable=out_dir_var)
154-
entry_dir.pack(side="left", fill="x", expand=True, padx=(8, 6))
155-
tk.Button(frm, text="Wybierz…", command=choose_dir).pack(side="left")
156-
157-
# start + status
158-
btn_start = tk.Button(root, text="Start", command=start)
159-
btn_start.pack(padx=12, pady=6)
160-
prog = ttk.Progressbar(root, mode="indeterminate")
161-
prog.pack(fill="x", padx=12, pady=(4, 8))
162-
status = tk.StringVar(value="Gotowy")
163-
tk.Label(root, textvariable=status, anchor="w").pack(fill="x", padx=12, pady=(0, 8))
164-
165-
root.mainloop()
138+
def build_ui():
139+
global root, entry_query, btn_start, prog, status, out_dir_var
140+
root = tk.Tk()
141+
root.title("GinioCrawler")
142+
root.geometry("560x220")
143+
tk.Label(root, text="Fraza do wyszukania:").pack(anchor="w", padx=12, pady=(12, 0))
144+
entry_query = tk.Entry(root); entry_query.pack(fill="x", padx=12, pady=6); entry_query.focus()
145+
frm = tk.Frame(root); frm.pack(fill="x", padx=12, pady=(0, 6))
146+
tk.Label(frm, text="Folder wyjściowy:").pack(side="left")
147+
out_dir_var = tk.StringVar(value=str((Path.cwd() / "wyniki")))
148+
entry_dir = tk.Entry(frm, textvariable=out_dir_var); entry_dir.pack(side="left", fill="x", expand=True, padx=(8, 6))
149+
tk.Button(frm, text="Wybierz…", command=choose_dir).pack(side="left")
150+
btn_start = tk.Button(root, text="Start", command=start); btn_start.pack(padx=12, pady=6)
151+
prog = ttk.Progressbar(root, mode="indeterminate"); prog.pack(fill="x", padx=12, pady=(4, 8))
152+
status = tk.StringVar(value="Gotowy"); tk.Label(root, textvariable=status, anchor="w").pack(fill="x", padx=12, pady=(0, 8))
153+
return root
154+
155+
if __name__ == "__main__":
156+
build_ui()
157+
root.mainloop()

main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ async def run(query):
150150
try:
151151
import os, sys, csv, asyncio
152152
from pathlib import Path
153-
from datetime import datetime # ważne: klasa datetime, nie moduł
153+
from datetime import datetime
154154

155155
q = input("Podaj szukane słowo: ").strip()
156156
if not q:

requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ freezegun
2424

2525
dirty-equals
2626

27-
pytest-cov
27+
pytest-cov
28+
29+
pytest

tests/.gitkeep

Lines changed: 0 additions & 1 deletion
This file was deleted.

tests/pytest.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[pytest]
2+
addopts = -q
3+
markers =
4+
slow: wolniejsze testy sieci/Playwright

tests/test_crawl_run.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import httpx, pytest, respx
2+
from main import crawl_one, run
3+
4+
@pytest.mark.asyncio
5+
@respx.mock
6+
async def test_crawl_one_merges_contact(monkeypatch):
7+
monkeypatch.setattr("ginio.in_robots", lambda url: True)
8+
respx.get("https://site.test/").mock(return_value=httpx.Response(200, text="""
9+
<a href="/contact">Contact</a>
10+
11+
"""))
12+
respx.get("https://site.test/contact").mock(return_value=httpx.Response(200, text="""
13+
<p>[email protected] 123 456 789</p>
14+
"""))
15+
async with httpx.AsyncClient() as client:
16+
out = await crawl_one("https://site.test/", client)
17+
assert "[email protected]" in out["emails"] and "[email protected]" in out["emails"]
18+
assert any("123" in p for p in out["phones"])
19+
20+
@pytest.mark.asyncio
21+
@respx.mock
22+
async def test_run_full(monkeypatch):
23+
respx.get("https://serpapi.com/search").mock(return_value=httpx.Response(
24+
200, json={"organic_results":[{"link":"https://a.pl"}, {"link":"https://b.pl"}]}
25+
))
26+
# stub robots + strony
27+
monkeypatch.setattr("ginio.in_robots", lambda url: True)
28+
respx.get("https://a.pl").mock(return_value=httpx.Response(200, text="<title>A</title>"))
29+
respx.get("https://b.pl").mock(return_value=httpx.Response(200, text="<title>B</title>"))
30+
monkeypatch.setenv("SERPAPI_KEY","x")
31+
results = await run("foo")
32+
assert {r["title"] for r in results} == {"A","B"}

tests/test_excel.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from pathlib import Path
2+
import pandas as pd
3+
from main import save_results
4+
5+
def test_save_results_creates_files(tmp_path, monkeypatch):
6+
root_out = tmp_path/"wyniki"
7+
data = [{"url":"https://x","title":"X","emails":["a@x"],"phones":["123"],"contact_url":None}]
8+
from main import write_excel as real_write_excel
9+
xlsx_called = {}
10+
def fake_write(csv, xlsx):
11+
xlsx_called["csv"] = csv; xlsx_called["xlsx"] = xlsx
12+
return real_write_excel(csv, xlsx)
13+
monkeypatch.setattr("ginio.write_excel", fake_write)
14+
csv_p, xlsx_p = save_results(data, "20250101_000000", root_out)
15+
assert Path(csv_p).exists() and Path(xlsx_p).exists()
16+
df = pd.read_excel(xlsx_p)
17+
assert set(df.columns) >= {"url","title","emails","phones","contact_url"}

0 commit comments

Comments
 (0)