Skip to content
Closed
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
13 changes: 11 additions & 2 deletions server/app/schemas/block.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from datetime import datetime
from datetime import datetime, timezone
from enum import Enum

from pydantic import BaseModel, ConfigDict
from pydantic import BaseModel, ConfigDict, field_validator


class BlockType(str, Enum):
Expand All @@ -18,6 +18,15 @@ class BlockBase(BaseModel):
block_type: BlockType # Use the Enum here
transportation_type: str | None = None

@field_validator("start_time", "end_time")
@classmethod
def validate_timezone(cls, v: datetime | None) -> datetime | None:
if v is None:
return None
if v.tzinfo is not None:
v = v.astimezone(timezone.utc).replace(tzinfo=None)
return v


class BlockCreate(BlockBase):
pass
Expand Down
59 changes: 59 additions & 0 deletions server/tests/schemas/test_block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from datetime import datetime, timezone
from app.schemas.block import BlockCreate, BlockType

def test_timezone_naive_input():
# Naive string: 2023-01-01T10:00:00
data = {
"title": "Naive",
"start_time": "2023-01-01T10:00:00",
"end_time": "2023-01-01T12:00:00",
"detail": "detail",
"block_type": "event",
}
block = BlockCreate(**data)
assert block.start_time.tzinfo is None
assert block.start_time == datetime(2023, 1, 1, 10, 0, 0)
assert block.end_time.tzinfo is None
assert block.end_time == datetime(2023, 1, 1, 12, 0, 0)

def test_timezone_aware_utc_input():
# Aware UTC string: 2023-01-01T10:00:00Z
data = {
"title": "Aware UTC",
"start_time": "2023-01-01T10:00:00Z",
"end_time": "2023-01-01T12:00:00Z",
"detail": "detail",
"block_type": "event",
}
block = BlockCreate(**data)
# Pydantic parses Z as UTC aware. The validator should convert to naive UTC.
assert block.start_time.tzinfo is None
assert block.start_time == datetime(2023, 1, 1, 10, 0, 0)
assert block.end_time.tzinfo is None
assert block.end_time == datetime(2023, 1, 1, 12, 0, 0)

def test_timezone_aware_offset_input():
# Aware Offset string: 2023-01-01T19:00:00+09:00 (JST) -> 10:00 UTC
data = {
"title": "Aware Offset",
"start_time": "2023-01-01T19:00:00+09:00",
"end_time": "2023-01-01T21:00:00+09:00",
"detail": "detail",
"block_type": "event",
}
block = BlockCreate(**data)
# The validator should convert to UTC and make naive.
assert block.start_time.tzinfo is None
assert block.start_time == datetime(2023, 1, 1, 10, 0, 0)
assert block.end_time.tzinfo is None
assert block.end_time == datetime(2023, 1, 1, 12, 0, 0)
Comment on lines +1 to +49

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

3つのテストケース(test_timezone_naive_input, test_timezone_aware_utc_input, test_timezone_aware_offset_input)は、テストロジックがほぼ同じです。pytest.mark.parametrizeを使用することで、これらのテストを1つのパラメータ化されたテストにまとめることができます。これにより、コードの重複が減り、保守性が向上します。これは、スタイルガイドの「保守性」と「一貫性」の原則にも合致します。

提案コードでは、3つのテストを1つにまとめ、関連するimport文も整理しています。

import pytest
from datetime import datetime
from app.schemas.block import BlockCreate


@pytest.mark.parametrize(
    ("test_id", "start_time_in", "end_time_in", "expected_start", "expected_end"),
    [
        (
            "naive",
            "2023-01-01T10:00:00",
            "2023-01-01T12:00:00",
            datetime(2023, 1, 1, 10, 0, 0),
            datetime(2023, 1, 1, 12, 0, 0),
        ),
        (
            "aware_utc",
            "2023-01-01T10:00:00Z",
            "2023-01-01T12:00:00Z",
            datetime(2023, 1, 1, 10, 0, 0),
            datetime(2023, 1, 1, 12, 0, 0),
        ),
        (
            "aware_offset",
            "2023-01-01T19:00:00+09:00",
            "2023-01-01T21:00:00+09:00",
            datetime(2023, 1, 1, 10, 0, 0),
            datetime(2023, 1, 1, 12, 0, 0),
        ),
    ],
)
def test_datetime_validation(test_id, start_time_in, end_time_in, expected_start, expected_end):
    """Datetime validation should handle naive, UTC, and offset-aware inputs."""
    data = {
        "title": f"Test {test_id}",
        "start_time": start_time_in,
        "end_time": end_time_in,
        "detail": "detail",
        "block_type": "event",
    }
    block = BlockCreate(**data)
    assert block.start_time.tzinfo is None
    assert block.start_time == expected_start
    assert block.end_time.tzinfo is None
    assert block.end_time == expected_end
References
  1. コードは、将来の修正や機能拡張が容易に行えるように設計します (保守性: 9行目) (link)
  2. すべてのプロジェクトで一貫したスタイルを保つことで、共同作業を円滑にし、エラーを削減します (一貫性: 10行目) (link)


def test_optional_end_time():
data = {
"title": "Optional End Time",
"start_time": "2023-01-01T10:00:00Z",
"detail": "detail",
"block_type": "event",
}
block = BlockCreate(**data)
assert block.end_time is None
Loading