Загрузка данных


сцен с 8 степ
        with allure.step("Этап 8. Извлечение строк данных из отчёта"):
            report_state.actual_data_rows = rejection_report_utils.iter_rejection_report_rows(
                report_state.actual_worksheet
            )
            report_state.actual_monitored_tag_rows = rejection_report_utils.filter_rows_by_monitored_tags(
                report_state.actual_data_rows,
                RejectionSensorTag,
            )
            allure.attach(
                rejection_report_utils.format_rejection_rows_for_allure(report_state.actual_monitored_tag_rows),
                name="Строки отчёта по тегам RejectionSensorTag",
                attachment_type=allure.attachment_type.TEXT,
            )

        with allure.step("Подготовка данных шапки xlsx для проверки"):
            title_info = report_state.actual_title_info
            report_state.actual_header_column_headers = report_utils.get_report_column_headers(
                report_state.actual_worksheet,
                headers_row=RejectedReportConst.REPORT_COLUMN_HEADERS_ROW,
            )
            report_state.actual_header_period_start = title_info.period_start
            report_state.actual_header_period_end = title_info.period_end
            report_state.actual_header_contains_expected_title = (
                rejection_report_utils.report_header_contains_expected_title(title_info.raw_title)
            )

        with allure.step("Подготовка данных для проверки строк отчёта по RejectionTestCase"):
            report_state.actual_case_checks = rejection_report_utils.prepare_rejection_report_case_checks(
                report_state.actual_monitored_tag_rows,
                cfg.rejection_cases,
                imitator_start_time,
            )

        with allure.step("Проверка первой строки шапки xlsx-отчёта"):
            StepCheck("Лист xlsx открыт", "worksheet").actual(report_state.actual_worksheet).is_not_none()
            with SoftAssertions() as soft_failures:
                StepCheck(
                    "Первая строка шапки содержит заголовок отчёта об отбракованных входных данных",
                    "report_title",
                    soft_failures,
                ).actual(report_state.actual_header_contains_expected_title).expected(True).equal_to()
                StepCheck(
                    "Время начала периода в первой строке шапки совпадает с фильтром запроса (+-1 мин)",
                    "period_start",
                    soft_failures,
                ).actual(report_state.actual_header_period_start).is_between(period_start_lo, period_start_hi)
                StepCheck(
                    "Время конца периода в первой строке шапки совпадает с фильтром запроса (+-1 мин)",
                    "period_end",
                    soft_failures,
                ).actual(report_state.actual_header_period_end).is_between(period_end_lo, period_end_hi)
                StepCheck(
                    "Названия колонок во второй строке шапки отчёта",
                    "column_headers",
                    soft_failures,
                ).actual(
                    report_state.actual_header_column_headers
                ).expected(RejectedReportConst.EXPECTED_COLUMN_HEADERS).equal_to()

        with allure.step("Проверка строк отчёта по каждому RejectionTestCase из конфигурации набора"):
            with SoftAssertions() as soft_failures:
                for case_check in report_state.actual_case_checks:
                    StepCheck(
                        f"В отчёте найдена отбраковка для {case_check.case_label} в интервале времени "
                        f"{case_check.window_start} - {case_check.window_end}",
                        RejectedReportConst.COL_TAG,
                        soft_failures,
                    ).actual(case_check.row_found).is_true_with_details(
                        expected_text=(
                            f"найдена строка с тегом {case_check.tag_description} "
                            f"и событием '{case_check.report_event}'"
                        ),
                        actual_text=case_check.found_row_summary,
                    )

                    if not case_check.row_found:
                        continue

                    StepCheck(
                        f"Для {case_check.case_label} время получения отбраковки в допустимом диапазоне",
                        RejectedReportConst.COL_DATETIME,
                        soft_failures,
                    ).actual(case_check.datetime_in_window).is_true_with_details(
                        expected_text=(
                            f"дата и время в диапазоне {case_check.window_start} — {case_check.window_end}"
                        ),
                        actual_text=case_check.datetime_actual_text,
                    )

                    StepCheck(
                        f"Для {case_check.case_label} суммарная продолжительность отбраковки "
                        f"({case_check.expected_duration_text}) совпадает",
                        RejectedReportConst.COL_DURATION,
                        soft_failures,
                    ).actual(case_check.actual_duration_seconds).expected(
                        case_check.expected_duration_seconds
                    ).equal_to()

                    StepCheck(
                        f"Для {case_check.case_label} участок трубопровода в колонке "
                        f"'{RejectedReportConst.COL_OBJECT}' не пустой",
                        RejectedReportConst.COL_OBJECT,
                        soft_failures,
                    ).actual(case_check.pipe_section).is_not_empty()

                    StepCheck(
                        f"Для {case_check.case_label} после последней точки в колонке "
                        f"'{RejectedReportConst.COL_OBJECT}' указан сигнал '{case_check.expected_signal_suffix}'",
                        RejectedReportConst.COL_OBJECT,
                        soft_failures,
                    ).actual(case_check.actual_signal_suffix).expected(
                        case_check.expected_signal_suffix
                    ).equal_to()

    except Exception:
        with allure.step("Прикрепление xlsx отчёта к Allure при падении теста"):





















report utils xslsx
@dataclass
class RejectionReportCaseCheck:
    """Подготовленные данные для проверки одного RejectionTestCase в xlsx-отчёте."""

    case_label: str
    tag_description: str
    report_event: str
    window_start: datetime
    window_end: datetime
    row_found: bool
    found_row_summary: str
    datetime_in_window: bool = False
    datetime_actual_text: str = "(пусто)"
    actual_duration_seconds: int = 0
    expected_duration_seconds: int = 0
    expected_duration_text: str = ""
    pipe_section: str = ""
    actual_signal_suffix: str = ""
    expected_signal_suffix: str = ""


def prepare_rejection_report_case_checks(
    monitored_rows: Iterable[RejectionReportRow],
    rejection_cases: Iterable[RejectionTestCase],
    imitator_start_time: datetime,
) -> List[RejectionReportCaseCheck]:
    """Собирает все вычисленные значения для проверки строк отчёта по кейсам набора."""
    case_checks: List[RejectionReportCaseCheck] = []

    for rejection_case in rejection_cases:
        report_event = expected_event_to_report_event(rejection_case.expected_event)
        window_start, window_end = get_case_time_window(imitator_start_time, rejection_case)
        case_label = f"события '{report_event}' - {rejection_case.sensor.description}"
        expected_signal_suffix = report_signal_suffix_by_expected_name(rejection_case.expected_signal_name)

        raw_case_rows = filter_rows_for_rejection_case(monitored_rows, rejection_case, imitator_start_time)
        merged_case_rows = merge_rejection_rows(raw_case_rows)
        primary_row = select_primary_merged_row(merged_case_rows)

        if primary_row is None:
            case_checks.append(
                RejectionReportCaseCheck(
                    case_label=case_label,
                    tag_description=rejection_case.sensor.description,
                    report_event=report_event,
                    window_start=window_start,
                    window_end=window_end,
                    row_found=False,
                    found_row_summary="строка не найдена",
                    expected_signal_suffix=expected_signal_suffix,
                )
            )
            continue

        merge_key = build_merge_key(primary_row)
        expected_duration_seconds = sum_duration_for_merge_key(raw_case_rows, merge_key)
        pipe_section, actual_signal_suffix = split_object_column(primary_row.object_value)
        datetime_in_window = is_datetime_within_closed_interval(
            primary_row.datetime_value,
            window_start,
            window_end,
        )

        case_checks.append(
            RejectionReportCaseCheck(
                case_label=case_label,
                tag_description=rejection_case.sensor.description,
                report_event=report_event,
                window_start=window_start,
                window_end=window_end,
                row_found=True,
                found_row_summary=(
                    f"{primary_row.tag_value} | {primary_row.event_value} | {primary_row.datetime_value}"
                ),
                datetime_in_window=datetime_in_window,
                datetime_actual_text=str(primary_row.datetime_value) if primary_row.datetime_value else "(пусто)",
                actual_duration_seconds=primary_row.duration_seconds,
                expected_duration_seconds=expected_duration_seconds,
                expected_duration_text=format_duration_seconds(expected_duration_seconds),
                pipe_section=pipe_section,
                actual_signal_suffix=actual_signal_suffix,
                expected_signal_suffix=expected_signal_suffix,
            )
        )

    return case_checks


def expected_event_to_report_event(expected_event: str) -> str:
















модели тестов
    actual_monitored_tag_rows: list[RejectionReportRow] = field(default_factory=list)
    actual_header_column_headers: list[str] = field(default_factory=list)
    actual_header_period_start: Optional[datetime] = None
    actual_header_period_end: Optional[datetime] = None
    actual_header_contains_expected_title: bool = False
    actual_case_checks: list = field(default_factory=list)