odoo-timesheets/tests/test_parser.py

150 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import pytest
from timesheets.parser import (
aggregate_rows,
build_description,
detect_has_duration_column,
parse_table,
)
# ---------------------------------------------------------------------------
# Fixtures / shared data
# ---------------------------------------------------------------------------
WITH_DURATION = [
"| Start | End | Duration | Project | Story | Note |",
"|-------|-------|----------|---------|-------------|---------|",
"| 08:00 | 08:30 | 00:30 | bugs | story one | |",
"| 08:30 | 09:00 | 00:30 | bugs | story one | |",
"| 09:00 | 09:15 | 00:15 | scrum | | dsu |",
]
WITHOUT_DURATION = [
"| Start | End | Project | Story | Note |",
"|-------|-------|---------|-------------|---------|",
"| 08:00 | 08:30 | bugs | story one | |",
"| 08:30 | 09:15 | scrum | | dsu |",
]
# ---------------------------------------------------------------------------
# detect_has_duration_column
# ---------------------------------------------------------------------------
class TestDetectHasDurationColumn:
def test_with_duration(self):
assert detect_has_duration_column(WITH_DURATION) is True
def test_without_duration(self):
assert detect_has_duration_column(WITHOUT_DURATION) is False
def test_no_header_defaults_to_true(self):
assert detect_has_duration_column(["no table here"]) is True
def test_case_insensitive(self):
lines = ["| Start | End | DURATION | Project | Story | Note |"]
assert detect_has_duration_column(lines) is True
# ---------------------------------------------------------------------------
# parse_table
# ---------------------------------------------------------------------------
class TestParseTable:
def test_with_duration_column(self):
rows = parse_table(WITH_DURATION, has_duration_col=True)
assert len(rows) == 3
assert rows[0]["project"] == "bugs"
assert rows[0]["duration_hours"] == 0.5
assert rows[2]["project"] == "scrum"
assert rows[2]["note"] == "dsu"
def test_without_duration_column(self):
rows = parse_table(WITHOUT_DURATION, has_duration_col=False)
assert len(rows) == 2
assert rows[0]["duration_hours"] == 0.5 # 08:0008:30
assert rows[1]["duration_hours"] == 0.75 # 08:3009:15
def test_header_row_skipped(self):
rows = parse_table(WITH_DURATION)
assert all(r["start"] != "Start" for r in rows)
def test_separator_row_skipped(self):
rows = parse_table(WITH_DURATION)
assert all(r["start"] != "---" for r in rows)
def test_markdown_link_stripped_in_story(self):
lines = [
"| Start | End | Duration | Project | Story | Note |",
"|-------|-------|----------|---------|----------------------------|------|",
"| 08:00 | 08:30 | 00:30 | bugs | [ticket 1](:/abc123) | |",
]
rows = parse_table(lines)
assert rows[0]["story"] == "ticket 1"
def test_invalid_duration_row_skipped(self):
lines = [
"| Start | End | Duration | Project | Story | Note |",
"|-------|-------|----------|---------|-------|------|",
"| 08:00 | 08:30 | bad | bugs | | |",
]
assert parse_table(lines) == []
def test_empty_input(self):
assert parse_table([]) == []
def test_non_table_lines_ignored(self):
lines = ["# My Timesheet", "", "Some prose."] + WITH_DURATION
rows = parse_table(lines)
assert len(rows) == 3
# ---------------------------------------------------------------------------
# build_description
# ---------------------------------------------------------------------------
class TestBuildDescription:
def test_story_and_note(self):
assert build_description("story", "note") == "story - note"
def test_story_only(self):
assert build_description("story", "") == "story"
def test_note_only(self):
assert build_description("", "note") == "note"
def test_both_empty(self):
assert build_description("", "") == "/"
def test_strips_whitespace(self):
assert build_description(" story ", " note ") == "story - note"
# ---------------------------------------------------------------------------
# aggregate_rows
# ---------------------------------------------------------------------------
class TestAggregateRows:
def test_same_project_story_summed(self):
rows = parse_table(WITH_DURATION)
aggregated = aggregate_rows(rows)
bugs = next(e for e in aggregated if e["project"] == "bugs")
assert bugs["quantity"] == 1.0 # 00:30 + 00:30
def test_distinct_entries_preserved(self):
rows = parse_table(WITH_DURATION)
aggregated = aggregate_rows(rows)
assert len(aggregated) == 2 # bugs/story-one and scrum/dsu
def test_insertion_order_preserved(self):
rows = parse_table(WITH_DURATION)
aggregated = aggregate_rows(rows)
assert aggregated[0]["project"] == "bugs"
assert aggregated[1]["project"] == "scrum"
def test_empty_input(self):
assert aggregate_rows([]) == []