Newer
Older
client_dwatch / lib / clean-logs / __tests__ / detail-log-date-range.test.ts
/**
 * 清掃詳細ログ画面の期間(D-159 追記 / 指示書AV 段階5b-6)
 *
 * ★画面(`DetailLogScreen.tsx`)は vitest の `include` に入らない★ので、URL の組み立てと
 * 検証の条件を切り出した `lib/clean-logs/detail-log-date-range.ts` をここで固定する。
 *
 * | # | dateFrom | dateTo | URL に付く期間 | 画面の検証 | 確かめること |
 * |---|---|---|---|---|---|
 * | P1 | "" | "" | なし | なし | 初期値(全期間)。`date_from=` の空文字も送らない |
 * | P2 | 2026-10-01 | "" | date_from だけ | なし | 片方だけでも検証エラーにせず、入っている側だけ送る(5b 以前と同じ) |
 * | P3 | "" | 2026-10-05 | date_to だけ | なし | 同上 |
 * | P4 | 2026-10-01 | 2026-10-05 | 両方 | なし | 従来どおり |
 * | P5 | 2026-10-05 | 2026-10-01 | 両方 | 開始>終了 | 従来どおり |
 * | P6 | 上限ちょうど | | 両方 | なし | 上限は含む |
 * | P7 | 上限+1日 | | 両方 | 上限超過 | 従来どおり |
 */
import { describe, expect, it } from "vitest";
import {
  appendDateRangeParams,
  dateRangeError,
} from "@/lib/clean-logs/detail-log-date-range";
import { parseCleanLogQuery } from "@/lib/clean-logs/query";
import { MAX_DATE_RANGE_DAYS } from "@/lib/config";

function dateParams(dateFrom: string, dateTo: string): URLSearchParams {
  const params = new URLSearchParams();
  appendDateRangeParams(params, dateFrom, dateTo);
  return params;
}

/** from から n日(両端含む)の終了日。`toISOString()` は UTC だが、正午 UTC 起点なので日付はずれない */
function endForDays(from: string, n: number): string {
  const start = Date.parse(`${from}T12:00:00Z`);
  return new Date(start + (n - 1) * 86_400_000).toISOString().slice(0, 10);
}

describe("appendDateRangeParams — 空の側は付けない", () => {
  it("P1: 両方空(画面の初期値)なら期間のパラメータは1つも付かない", () => {
    const params = dateParams("", "");
    expect(params.has("date_from")).toBe(false);
    expect(params.has("date_to")).toBe(false);
    expect(params.toString()).toBe("");
  });

  it("P2: date_from だけなら date_from だけ", () => {
    const params = dateParams("2026-10-01", "");
    expect(params.get("date_from")).toBe("2026-10-01");
    expect(params.has("date_to")).toBe(false);
  });

  it("P3: date_to だけなら date_to だけ", () => {
    const params = dateParams("", "2026-10-05");
    expect(params.has("date_from")).toBe(false);
    expect(params.get("date_to")).toBe("2026-10-05");
  });

  it("P4: 両方入っていれば両方(従来どおり)", () => {
    expect(dateParams("2026-10-01", "2026-10-05").toString()).toBe(
      "date_from=2026-10-01&date_to=2026-10-05"
    );
  });
});

describe("dateRangeError — 両方入っているときだけ検証する", () => {
  it.each([
    ["P1", "", ""],
    ["P2", "2026-10-01", ""],
    ["P3", "", "2026-10-05"],
    // 片方だけ入れて、もう片方が「開始>終了」になりそうな値でも止めない
    ["P2'", "2099-12-31", ""],
  ])("%s: 片方以上が空なら null", (_id, from, to) => {
    expect(dateRangeError(from, to, MAX_DATE_RANGE_DAYS)).toBeNull();
  });

  it("P4: 正しい範囲なら null", () => {
    expect(dateRangeError("2026-10-01", "2026-10-05", MAX_DATE_RANGE_DAYS)).toBeNull();
    // 開始=終了(1日)も通す
    expect(dateRangeError("2026-10-05", "2026-10-05", MAX_DATE_RANGE_DAYS)).toBeNull();
  });

  it("P5: 開始>終了は従来の文言", () => {
    expect(dateRangeError("2026-10-05", "2026-10-01", MAX_DATE_RANGE_DAYS)).toBe(
      "date_fromはdate_to以前の日付を指定してください"
    );
  });

  it("P6: 上限ちょうどは通す", () => {
    const to = endForDays("2026-01-01", MAX_DATE_RANGE_DAYS);
    expect(dateRangeError("2026-01-01", to, MAX_DATE_RANGE_DAYS)).toBeNull();
  });

  it("P7: 上限+1日は従来の文言", () => {
    const to = endForDays("2026-01-01", MAX_DATE_RANGE_DAYS + 1);
    expect(dateRangeError("2026-01-01", to, MAX_DATE_RANGE_DAYS)).toBe(
      `date_from〜date_toは${MAX_DATE_RANGE_DAYS}日以内で指定してください`
    );
  });
});

describe("画面の検証とサーバの検証(parseCleanLogQuery)が食い違わない", () => {
  it.each([
    ["P1", "", ""],
    ["P2", "2026-10-01", ""],
    ["P3", "", "2026-10-05"],
    ["P4", "2026-10-01", "2026-10-05"],
    ["P5", "2026-10-05", "2026-10-01"],
    ["P6", "2026-01-01", endForDays("2026-01-01", MAX_DATE_RANGE_DAYS)],
    ["P7", "2026-01-01", endForDays("2026-01-01", MAX_DATE_RANGE_DAYS + 1)],
  ])("%s: 画面が止めるのはサーバが 422 を返すときだけ(文言も同じ)", (_id, from, to) => {
    const clientError = dateRangeError(from, to, MAX_DATE_RANGE_DAYS);
    const parsed = parseCleanLogQuery(dateParams(from, to));
    if (clientError === null) {
      expect(parsed.ok).toBe(true);
    } else {
      expect(parsed.ok).toBe(false);
      if (!parsed.ok) expect(parsed.error.error.message).toBe(clientError);
    }
  });

  it("P1: 期間なしはサーバでも日付の条件なし(全期間)", () => {
    const parsed = parseCleanLogQuery(dateParams("", ""));
    expect(parsed.ok).toBe(true);
    if (parsed.ok) {
      expect(parsed.query.dateFrom).toBeNull();
      expect(parsed.query.dateTo).toBeNull();
    }
  });
});