2026-02-22 18:15:25 +01:00
|
|
|
import uuid
|
2026-02-22 18:08:39 +01:00
|
|
|
from typing import Generator, Sequence
|
|
|
|
|
|
2026-02-19 20:38:29 +01:00
|
|
|
import pytest
|
2026-02-22 18:08:39 +01:00
|
|
|
from sqlalchemy import Engine
|
|
|
|
|
from sqlmodel import Session
|
2026-02-19 20:38:29 +01:00
|
|
|
|
2026-02-22 18:08:39 +01:00
|
|
|
from teilchensammler_cli.models import (
|
|
|
|
|
Teilchen,
|
|
|
|
|
TeilchenCreate,
|
|
|
|
|
load_initial_data,
|
|
|
|
|
make_teilchen_input,
|
2026-02-22 18:15:25 +01:00
|
|
|
add_to_database,
|
2026-02-22 18:08:39 +01:00
|
|
|
)
|
2026-02-19 20:38:29 +01:00
|
|
|
|
|
|
|
|
|
2026-02-20 21:24:50 +01:00
|
|
|
@pytest.mark.final # don't run while we are fiddling with the app
|
2026-02-16 21:11:35 +01:00
|
|
|
def test_initial_layout(snap_compare):
|
2026-01-08 12:08:13 +01:00
|
|
|
from teilchensammler_cli.main import app
|
|
|
|
|
|
|
|
|
|
assert snap_compare(app, terminal_size=(130, 40))
|
2026-02-19 20:38:29 +01:00
|
|
|
|
|
|
|
|
|
2026-02-22 18:08:39 +01:00
|
|
|
empty_teilchen_data = {
|
2026-02-19 20:38:29 +01:00
|
|
|
"name": "",
|
|
|
|
|
"description": "",
|
|
|
|
|
"tags": "",
|
|
|
|
|
"text": "",
|
|
|
|
|
"number": 1,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-02-20 21:24:50 +01:00
|
|
|
def TC(**kwargs) -> TeilchenCreate | None:
|
|
|
|
|
"""TC = TeilchenCreate
|
|
|
|
|
|
|
|
|
|
Create Teilchen with desired attributes.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
input: mapping that must be well-formed enough to actually create a Teilchen
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
- `None` on empty input
|
|
|
|
|
- an instance of `TeilchenCreate`
|
|
|
|
|
"""
|
|
|
|
|
if kwargs:
|
2026-02-22 18:08:39 +01:00
|
|
|
arguments = empty_teilchen_data | kwargs
|
2026-02-20 21:24:50 +01:00
|
|
|
return TeilchenCreate(**arguments) # ty:ignore[invalid-argument-type]
|
|
|
|
|
|
|
|
|
|
|
2026-02-19 20:38:29 +01:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"example_input,expected",
|
|
|
|
|
[
|
2026-02-20 21:24:50 +01:00
|
|
|
# Not enough data
|
|
|
|
|
("", TC()),
|
|
|
|
|
(".", TC()),
|
|
|
|
|
("..", TC()),
|
|
|
|
|
(".a.", TC()),
|
|
|
|
|
("a", TC()),
|
|
|
|
|
("aa", TC()),
|
2026-02-22 18:08:39 +01:00
|
|
|
(" .", TC()), # still an empty string
|
2026-02-20 21:24:50 +01:00
|
|
|
# Just enough for "name"
|
|
|
|
|
("a.", TC(name="a", text="a.")),
|
|
|
|
|
("aa.", TC(name="aa", text="aa.")),
|
|
|
|
|
("a..", TC(name="a", text="a..")),
|
|
|
|
|
("a.b.", TC(name="a", text="a.b.")),
|
2026-02-22 18:08:39 +01:00
|
|
|
("1.", TC(name="1", text="1.")), # numbers can be names
|
|
|
|
|
("_.", TC(name="_", text="_.")), # underscores can be names
|
|
|
|
|
("a b.", TC(name="a b", text="a b.")), # names can contain spaces
|
2026-02-20 21:24:50 +01:00
|
|
|
# Just enough for "name" and "description"
|
|
|
|
|
('a."b"', TC(name="a", description="b", text='a."b"')),
|
|
|
|
|
('a. "b"', TC(name="a", description="b", text='a. "b"')),
|
|
|
|
|
('a. ""', TC(name="a", description="", text='a. ""')),
|
|
|
|
|
('. ""', TC()),
|
|
|
|
|
('. "b"', TC()),
|
|
|
|
|
# Just enough for "name" and "description" and "tags"
|
|
|
|
|
('a. "b" #c', TC(name="a", description="b", tags="#c", text='a. "b" #c')),
|
|
|
|
|
('a. "b #d" #c', TC(name="a", description="b #d", tags="#c #d", text='a. "b #d" #c')),
|
|
|
|
|
# swap order in input, tag result is stable
|
|
|
|
|
('a. "b #c" #d', TC(name="a", description="b #c", tags="#c #d", text='a. "b #c" #d')),
|
|
|
|
|
# Just enough for "name" and "tags"
|
|
|
|
|
("a. #d", TC(name="a", description="", tags="#d", text="a. #d")),
|
|
|
|
|
("a. #d#b", TC(name="a", description="", tags="#b #d", text="a. #d#b")),
|
|
|
|
|
("a. ##b", TC(name="a", description="", tags="#b", text="a. ##b")),
|
|
|
|
|
("a. #", TC(name="a", description="", tags="", text="a. #")),
|
|
|
|
|
("a. ##", TC(name="a", description="", tags="", text="a. ##")),
|
2026-02-22 18:08:39 +01:00
|
|
|
("a. #tag with spaces", TC(name="a", tags="#tag", text="a. #tag with spaces")),
|
2026-02-20 21:24:50 +01:00
|
|
|
# do we care about "number"?
|
2026-02-19 20:38:29 +01:00
|
|
|
],
|
|
|
|
|
)
|
2026-02-20 21:24:50 +01:00
|
|
|
async def test_maketeilcheninput_can_create_desired_teilchen(
|
2026-02-20 22:54:02 +01:00
|
|
|
example_input: str, expected: TeilchenCreate | None
|
2026-02-19 20:38:29 +01:00
|
|
|
) -> None:
|
|
|
|
|
|
2026-02-20 21:24:50 +01:00
|
|
|
actual = await make_teilchen_input(example_input)
|
2026-02-19 20:38:29 +01:00
|
|
|
|
2026-02-20 21:24:50 +01:00
|
|
|
assert expected == actual
|
2026-02-22 18:08:39 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(name="engine")
|
|
|
|
|
def in_memory_db() -> Generator[Engine]:
|
|
|
|
|
"""Creates an in-memory sqlite database
|
|
|
|
|
|
|
|
|
|
Yields:
|
|
|
|
|
The engine used to create the database.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
engine = create_engine("sqlite:///:memory:")
|
|
|
|
|
|
|
|
|
|
yield engine
|
|
|
|
|
|
|
|
|
|
engine.dispose()
|
|
|
|
|
|
|
|
|
|
|
2026-02-22 18:57:01 +01:00
|
|
|
@pytest.fixture(name="app_db")
|
|
|
|
|
def database_ready_to_use(engine: Engine) -> Generator[Engine]:
|
|
|
|
|
SQLModel.metadata.create_all(engine)
|
|
|
|
|
yield engine
|
|
|
|
|
|
|
|
|
|
|
2026-02-22 18:08:39 +01:00
|
|
|
@pytest.fixture(name="db_teilchen")
|
2026-02-22 18:57:01 +01:00
|
|
|
def teilchen_in_db(app_db: Engine) -> Generator[Teilchen]:
|
2026-02-22 18:08:39 +01:00
|
|
|
"""Creates a new Teilchen and stores it in the database.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
engine: an instance of sqlalchemy.Engine which was used to create the database.
|
|
|
|
|
|
|
|
|
|
Yields:
|
|
|
|
|
The Teilchen refreshed from the database.
|
|
|
|
|
"""
|
|
|
|
|
import uuid
|
|
|
|
|
|
|
|
|
|
teilchen = Teilchen(
|
|
|
|
|
id=uuid.uuid7(), name="TT", description="Test Teilchen", number=1, tags="", text=""
|
|
|
|
|
)
|
2026-02-22 18:57:01 +01:00
|
|
|
with Session(app_db) as session:
|
2026-02-22 18:08:39 +01:00
|
|
|
session.add(teilchen)
|
|
|
|
|
session.commit()
|
|
|
|
|
session.refresh(teilchen)
|
|
|
|
|
|
|
|
|
|
yield teilchen
|
|
|
|
|
|
2026-02-22 18:57:01 +01:00
|
|
|
with Session(app_db) as session:
|
2026-02-22 18:08:39 +01:00
|
|
|
session.delete(teilchen)
|
|
|
|
|
session.commit()
|
|
|
|
|
|
|
|
|
|
|
2026-02-22 18:57:01 +01:00
|
|
|
async def test_loadinitialdata_returns_expected_data(app_db: Engine, db_teilchen: Teilchen):
|
|
|
|
|
all_data: Sequence[tuple] = await load_initial_data(app_db)
|
2026-02-22 18:08:39 +01:00
|
|
|
assert len(all_data) == 1
|
|
|
|
|
|
|
|
|
|
fetched_data: tuple = all_data[0]
|
|
|
|
|
teilchen_data: dict[str, str | int] = {
|
|
|
|
|
"id": fetched_data[0],
|
|
|
|
|
"name": fetched_data[1],
|
|
|
|
|
"description": fetched_data[2],
|
|
|
|
|
"number": 1,
|
|
|
|
|
"tags": "",
|
|
|
|
|
"text": "",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fetched_teilchen = Teilchen.model_validate(teilchen_data)
|
|
|
|
|
|
|
|
|
|
assert fetched_teilchen == db_teilchen
|
2026-02-22 18:15:25 +01:00
|
|
|
|
|
|
|
|
|
2026-02-22 18:57:01 +01:00
|
|
|
async def test_data_provided_to_addtodatabase_ends_up_in_database(app_db: Engine):
|
|
|
|
|
all_data = await load_initial_data(app_db)
|
2026-02-22 18:15:25 +01:00
|
|
|
assert len(all_data) == 0
|
|
|
|
|
|
|
|
|
|
teilchen = Teilchen(
|
|
|
|
|
id=uuid.uuid7(), name="test", description="test", tags="#test", number=1, text="test"
|
|
|
|
|
)
|
|
|
|
|
|
2026-02-22 18:57:01 +01:00
|
|
|
db_teilchen = await add_to_database(teilchen, app_db)
|
2026-02-22 18:15:25 +01:00
|
|
|
|
|
|
|
|
assert teilchen == db_teilchen
|