인앱 미니 블로그(AI 자동 포스트, 이메일 승인)·이용후기(즉시 게시)·예약 요청(메일 발송)을
새로 붙였고, 병행해서 /s/stay 목업과 발행 사이트 공통 렌더러(UnitsSection·FestivalSection·
LocalGuideSection·WeatherSection 등)의 UI 버그를 다수 고쳤다. 범위가 넓지만 한 주 분량
작업을 한 커밋으로 묶어 달라는 요청에 따라 하나로 묶는다.
- solution/backend: post/review/booking_request 라우터·서비스·CRUD 추가, 스케줄러에
블로그 초안 생성(새벽 4:10)·발송(아침 9:00) cron 등록, 마이그레이션 4건 추가
- solution/frontend, admin/frontend: 생성된 API 클라이언트 갱신, 리뷰 모더레이션·
블로그 글 관리 페이지 추가
- solution/site/src: 객실 상세+실시간예약(날짜선택·연락처 폼)을 모달로 통합, 축제·
주변안내 카드 클릭 시 모달 전환, 후기 목록 카드 UI, 공용 Modal 컴포넌트 신설,
날씨 문구 동기화 버그 수정(하늘줄·기온줄 한 타이머로), 시설·편의 가능/불가 아이콘
색상 하이라이트, 헤더 메뉴 순서를 실제 섹션 순서에 맞춤, 하단 탭바 아이콘 정렬 버그
(line-height) 수정, 추천일정 점선 연결+데스크톱 자동펼침/모바일 축소, 채널 라벨에
크롤링 원문("NOL")이 새던 것을 bookingLabel() 로 교체
- solution/site/scripts/mockup: /s/stay 패치 스크립트·주입 CSS·JS 다수 수정, stay4~6
빌드 스크립트 추가(다른 세션 작업)
테스트: solution/site `npx tsc --noEmit` 통과, `npx vitest run` 93 passed,
solution/backend `pytest tests/test_booking_request.py` 6 passed(로컬 DB 대상).
예약 요청 메일은 실제 발송까지 확인(place 66894a1b 소유자 이메일 누락을 DB에서 보정).
439 lines
19 KiB
Python
439 lines
19 KiB
Python
"""미니 블로그 — 빌더 앱 로그인 화면(이번 달 생성된 글). 기획: docs/MINI_BLOG.md
|
|
|
|
★ 이 파일이 지키는 것:
|
|
- 로그인한 사장님은 자기 사업장의 글만 본다(남의 가게 글이 섞이면 안 된다)
|
|
- 아직 메일이 안 나간 REVIEWED 글도 로그인 화면에서 바로 고치고 승인할 수 있다
|
|
- 여기서도 금칙 게이트는 그대로 탄다 — 로그인했다고 우회되지 않는다
|
|
"""
|
|
import uuid
|
|
from datetime import date, timedelta
|
|
|
|
from sqlalchemy import text
|
|
|
|
from common.enums import JobStatus, JobType, PostStatus, PostTopicKind, SiteStatus
|
|
|
|
BODY = (
|
|
"비가 한 차례 지나간 뒤 마당 돌이 검게 젖었습니다. 이런 날에는 대청마루에 앉아 빗소리만 들어도 "
|
|
"하루가 지나갑니다. 우산은 현관에 넉넉히 두었으니 편하게 다녀오세요. 젖은 길은 미끄러우니 "
|
|
"편한 신발을 권합니다. 마당 평상은 비가 그치면 금세 마릅니다."
|
|
)
|
|
|
|
|
|
async def _place(client, headers, name="블로그펜션") -> str:
|
|
r = await client.post("/v1/place", headers=headers, json={"name": name, "category": 1})
|
|
return r.json()["place"]["place_id"]
|
|
|
|
|
|
async def _seed_post(db_engine, place_id, *, status=PostStatus.REVIEWED, scheduled=None) -> str:
|
|
post_id = uuid.uuid4()
|
|
async with db_engine.begin() as conn:
|
|
await conn.execute(
|
|
text("INSERT INTO place_posts (post_id, place_id, body, topic_kind, topic_key, status, scheduled_date) "
|
|
"VALUES (:id, :pid, :body, :kind, :key, :st, :sched)"),
|
|
{"id": post_id, "pid": place_id, "body": BODY, "kind": PostTopicKind.WEATHER.value,
|
|
"key": f"weather:{post_id.hex[:6]}", "st": status.value, "sched": scheduled or date.today()},
|
|
)
|
|
return str(post_id)
|
|
|
|
|
|
async def _status(db_engine, post_id) -> int:
|
|
async with db_engine.begin() as conn:
|
|
row = await conn.execute(text("SELECT status FROM place_posts WHERE post_id = :id"), {"id": post_id})
|
|
return row.scalar()
|
|
|
|
|
|
async def test_owner_sees_this_months_posts(client, db_engine, auth_headers):
|
|
h = await auth_headers("blogowner1")
|
|
place_id = await _place(client, h)
|
|
await _seed_post(db_engine, place_id)
|
|
|
|
res = await client.get(f"/v1/place/{place_id}/post", headers=h)
|
|
|
|
body = res.json()
|
|
assert body["result"]["code"] == 0
|
|
assert len(body["posts"]) == 1
|
|
assert body["posts"][0]["body"] == BODY
|
|
|
|
|
|
async def test_owner_cannot_see_someone_elses_posts(client, db_engine, auth_headers):
|
|
owner = await auth_headers("blogowner2")
|
|
other = await auth_headers("blogowner3")
|
|
place_id = await _place(client, owner)
|
|
await _seed_post(db_engine, place_id)
|
|
|
|
res = await client.get(f"/v1/place/{place_id}/post", headers=other)
|
|
|
|
body = res.json()
|
|
assert body["result"]["success"] is False
|
|
assert body["posts"] == []
|
|
|
|
|
|
async def test_owner_can_edit_and_approve_before_mail_goes_out(client, db_engine, auth_headers):
|
|
"""REVIEWED 글(아직 메일 안 나감)도 로그인 화면에서 바로 고쳐 승인할 수 있다."""
|
|
h = await auth_headers("blogowner4")
|
|
place_id = await _place(client, h)
|
|
post_id = await _seed_post(db_engine, place_id, status=PostStatus.REVIEWED)
|
|
new_body = BODY.replace("빗소리", "새소리")
|
|
|
|
res = await client.put(f"/v1/place/{place_id}/post/{post_id}", headers=h, json={"body": new_body})
|
|
|
|
assert res.json()["result"]["success"] is True
|
|
assert await _status(db_engine, post_id) == PostStatus.APPROVED.value
|
|
|
|
|
|
async def test_owner_edit_rejects_unverifiable_claims(client, db_engine, auth_headers):
|
|
h = await auth_headers("blogowner5")
|
|
place_id = await _place(client, h)
|
|
post_id = await _seed_post(db_engine, place_id)
|
|
bad_body = BODY[:120] + " 주중 198,000원입니다."
|
|
|
|
res = await client.put(f"/v1/place/{place_id}/post/{post_id}", headers=h, json={"body": bad_body})
|
|
|
|
body = res.json()
|
|
assert body["result"]["success"] is False
|
|
assert "198,000원" in body["msg"]
|
|
assert await _status(db_engine, post_id) == PostStatus.REVIEWED.value
|
|
|
|
|
|
async def test_generate_now_creates_posts_for_published_site(client, db_engine, auth_headers, monkeypatch):
|
|
"""새벽 크론(04:10)을 기다리지 않고, 사장님이 고른 구간을 그 자리에서 채운다(2026-09-17,
|
|
사장님 지시: "지금 생성하기에서 시작이랑 끝 날짜를 정해야하지 않을까") — 발행된 사이트일 때만."""
|
|
from services import blog_service
|
|
|
|
async def fake_generate_one(*, place_name, region, topic_kind, material, used_topics):
|
|
return ("테스트로 만든 문구입니다. " + BODY, "gemini-test-model")
|
|
|
|
monkeypatch.setattr(blog_service, "generate_one", fake_generate_one)
|
|
|
|
h = await auth_headers("bloggen1")
|
|
place_id = await _place(client, h, name="즉시생성펜션")
|
|
async with db_engine.begin() as conn:
|
|
await conn.execute(
|
|
text("INSERT INTO sites (site_id, place_id, status) VALUES (:sid, :pid, :st)"),
|
|
{"sid": uuid.uuid4(), "pid": place_id, "st": SiteStatus.PUBLISHED.value},
|
|
)
|
|
start = date.today()
|
|
end = start + timedelta(days=29)
|
|
|
|
res = await client.post(
|
|
f"/v1/place/{place_id}/post/generate", headers=h,
|
|
params={"start": start.isoformat(), "end": end.isoformat()},
|
|
)
|
|
|
|
body = res.json()
|
|
assert body["result"]["success"] is True
|
|
assert body["requested"] == 30
|
|
assert body["created"] > 0
|
|
|
|
async with db_engine.begin() as conn:
|
|
count = (await conn.execute(
|
|
text("SELECT count(*) FROM place_posts WHERE place_id = :pid"), {"pid": place_id},
|
|
)).scalar()
|
|
dates = [row[0] for row in (await conn.execute(
|
|
text("SELECT scheduled_date FROM place_posts WHERE place_id = :pid ORDER BY scheduled_date"),
|
|
{"pid": place_id},
|
|
)).all()]
|
|
assert count == body["created"]
|
|
# 구간 안, 오늘부터 순서대로, 겹치는 날짜 없이.
|
|
assert dates[0] == start
|
|
assert dates == sorted(set(dates))
|
|
assert all(start <= d <= end for d in dates)
|
|
|
|
|
|
async def test_send_reviewed_only_mails_posts_due_today(client, db_engine, auth_headers, monkeypatch):
|
|
"""미래 날짜로 배정된 글은 그날이 오기 전엔 메일이 안 나간다."""
|
|
from services import blog_jobs, mail_service
|
|
|
|
monkeypatch.setattr(mail_service, "is_configured", lambda: True)
|
|
sent_calls = []
|
|
monkeypatch.setattr(mail_service, "send", lambda **kwargs: sent_calls.append(kwargs) or True)
|
|
|
|
h = await auth_headers("bloggen3")
|
|
place_id = await _place(client, h, name="예약펜션")
|
|
async with db_engine.begin() as conn:
|
|
await conn.execute(
|
|
text("INSERT INTO sites (site_id, place_id, status) VALUES (:sid, :pid, :st)"),
|
|
{"sid": uuid.uuid4(), "pid": place_id, "st": SiteStatus.PUBLISHED.value},
|
|
)
|
|
await conn.execute(text("UPDATE users SET email = :e WHERE id = :id"), {"e": "owner@example.com", "id": "bloggen3"})
|
|
|
|
today_post = await _seed_post(db_engine, place_id, scheduled=date.today())
|
|
future_post = await _seed_post(db_engine, place_id, scheduled=date.today() + timedelta(days=5))
|
|
|
|
sent = await blog_jobs.send_reviewed()
|
|
|
|
assert sent == 1
|
|
assert len(sent_calls) == 1
|
|
assert await _status(db_engine, today_post) == PostStatus.SENT.value
|
|
assert await _status(db_engine, future_post) == PostStatus.REVIEWED.value
|
|
|
|
|
|
async def test_owner_can_publish_as_is_without_editing(client, db_engine, auth_headers):
|
|
"""바로 발행 — 본문을 안 고쳐도 승인되고 재발행 잡이 걸린다."""
|
|
h = await auth_headers("blogowner6")
|
|
place_id = await _place(client, h)
|
|
post_id = await _seed_post(db_engine, place_id, status=PostStatus.REVIEWED)
|
|
|
|
res = await client.post(f"/v1/place/{place_id}/post/{post_id}/approve", headers=h)
|
|
|
|
assert res.json()["result"]["success"] is True
|
|
assert await _status(db_engine, post_id) == PostStatus.APPROVED.value
|
|
async with db_engine.begin() as conn:
|
|
body_row = (await conn.execute(
|
|
text("SELECT body FROM place_posts WHERE post_id = :id"), {"id": post_id},
|
|
)).scalar()
|
|
assert body_row == BODY
|
|
|
|
|
|
async def test_owner_cannot_publish_someone_elses_post(client, db_engine, auth_headers):
|
|
owner = await auth_headers("blogowner7")
|
|
other = await auth_headers("blogowner8")
|
|
place_id = await _place(client, owner)
|
|
post_id = await _seed_post(db_engine, place_id, status=PostStatus.REVIEWED)
|
|
|
|
res = await client.post(f"/v1/place/{place_id}/post/{post_id}/approve", headers=other)
|
|
|
|
assert res.json()["result"]["success"] is False
|
|
assert await _status(db_engine, post_id) == PostStatus.REVIEWED.value
|
|
|
|
|
|
async def test_generate_now_is_noop_for_unpublished_site(client, db_engine, auth_headers):
|
|
"""발행 전 사업장은 생성 스윕 대상이 아니다(blog_jobs._published_places) — 0건이어야 한다."""
|
|
h = await auth_headers("bloggen2")
|
|
place_id = await _place(client, h, name="발행전펜션")
|
|
start = date.today()
|
|
|
|
res = await client.post(
|
|
f"/v1/place/{place_id}/post/generate", headers=h,
|
|
params={"start": start.isoformat(), "end": start.isoformat()},
|
|
)
|
|
|
|
body = res.json()
|
|
assert body["result"]["success"] is True
|
|
assert body["created"] == 0
|
|
|
|
|
|
async def test_generate_now_rejects_end_before_start(client, db_engine, auth_headers):
|
|
h = await auth_headers("bloggen16")
|
|
place_id = await _place(client, h, name="구간역순펜션")
|
|
start = date.today()
|
|
|
|
res = await client.post(
|
|
f"/v1/place/{place_id}/post/generate", headers=h,
|
|
params={"start": start.isoformat(), "end": (start - timedelta(days=1)).isoformat()},
|
|
)
|
|
|
|
body = res.json()
|
|
assert body["result"]["success"] is False
|
|
|
|
|
|
async def test_approved_post_flags_build_failed_when_job_dead(client, db_engine, auth_headers):
|
|
"""화면은 발행완료/발행실패만 본다(사장님 지시) — 승인됐는데 BUILD 잡이 dead-letter 면
|
|
build_failed=true, 그 외(대기 중인 잡·아직 승인 전)에는 false 로 남는다."""
|
|
h = await auth_headers("bloggen4")
|
|
place_id = await _place(client, h, name="실패펜션")
|
|
failed_post = await _seed_post(db_engine, place_id, status=PostStatus.APPROVED)
|
|
|
|
async with db_engine.begin() as conn:
|
|
await conn.execute(
|
|
text("INSERT INTO jobs (job_type, status, payload) VALUES (:jt, :st, :pl)"),
|
|
{"jt": JobType.BUILD.value, "st": JobStatus.DEAD.value, "pl": f'{{"place_id": "{place_id}"}}'},
|
|
)
|
|
|
|
res = await client.get(f"/v1/place/{place_id}/post", headers=h)
|
|
|
|
posts = {p["post_id"]: p for p in res.json()["posts"]}
|
|
assert posts[failed_post]["build_failed"] is True
|
|
|
|
|
|
async def test_pending_build_job_does_not_flag_failure(client, db_engine, auth_headers):
|
|
h = await auth_headers("bloggen5")
|
|
place_id = await _place(client, h, name="대기펜션")
|
|
post_id = await _seed_post(db_engine, place_id, status=PostStatus.APPROVED)
|
|
|
|
async with db_engine.begin() as conn:
|
|
await conn.execute(
|
|
text("INSERT INTO jobs (job_type, status, payload) VALUES (:jt, :st, :pl)"),
|
|
{"jt": JobType.BUILD.value, "st": JobStatus.PENDING.value, "pl": f'{{"place_id": "{place_id}"}}'},
|
|
)
|
|
|
|
res = await client.get(f"/v1/place/{place_id}/post", headers=h)
|
|
|
|
posts = {p["post_id"]: p for p in res.json()["posts"]}
|
|
assert posts[post_id].get("build_failed", False) is False
|
|
|
|
|
|
async def test_upcoming_only_returns_next_week_in_date_order(client, db_engine, auth_headers):
|
|
"""상단 카로셀 — 오늘부터 N일치만, 날짜 오름차순. 그 뒤 배정분은 안 보인다."""
|
|
h = await auth_headers("bloggen6")
|
|
place_id = await _place(client, h, name="주간펜션")
|
|
far = await _seed_post(db_engine, place_id, scheduled=date.today() + timedelta(days=20))
|
|
tomorrow = await _seed_post(db_engine, place_id, scheduled=date.today() + timedelta(days=1))
|
|
today = await _seed_post(db_engine, place_id, scheduled=date.today())
|
|
|
|
res = await client.get(f"/v1/place/{place_id}/post/upcoming", headers=h, params={"days": 7})
|
|
|
|
ids = [p["post_id"] for p in res.json()["posts"]]
|
|
assert ids == [today, tomorrow]
|
|
assert far not in ids
|
|
|
|
|
|
async def test_get_post_by_id_for_mail_edit_link(client, db_engine, auth_headers):
|
|
"""메일 '수정하기' 링크(자동 로그인)가 postId 하나로 그 글을 바로 찾는 경로."""
|
|
h = await auth_headers("bloggen7")
|
|
place_id = await _place(client, h, name="단건조회펜션")
|
|
post_id = await _seed_post(db_engine, place_id)
|
|
|
|
res = await client.get(f"/v1/place/{place_id}/post/{post_id}", headers=h)
|
|
|
|
body = res.json()
|
|
assert body["result"]["success"] is True
|
|
assert len(body["posts"]) == 1
|
|
assert body["posts"][0]["post_id"] == post_id
|
|
|
|
|
|
async def test_get_post_by_id_scoped_to_owner(client, db_engine, auth_headers):
|
|
owner = await auth_headers("bloggen8")
|
|
other = await auth_headers("bloggen9")
|
|
place_id = await _place(client, owner, name="타인조회펜션")
|
|
post_id = await _seed_post(db_engine, place_id)
|
|
|
|
res = await client.get(f"/v1/place/{place_id}/post/{post_id}", headers=other)
|
|
|
|
body = res.json()
|
|
assert body["result"]["success"] is False
|
|
assert body["posts"] == []
|
|
|
|
|
|
async def test_generation_history_counts_by_batch(client, db_engine, auth_headers, monkeypatch):
|
|
"""생성 이력 — 한 번에 몇 건 · 어느 모델(사장님 지시: "생성이력도 있어야해 몇개
|
|
생성했는지" / "어느 모델썼는지 등등" → JSONB 한 칸(generation_meta)에 담는다)."""
|
|
from services import blog_service
|
|
|
|
async def fake_generate_one(*, place_name, region, topic_kind, material, used_topics):
|
|
return ("테스트로 만든 문구입니다. " + BODY, "gemini-test-model")
|
|
|
|
monkeypatch.setattr(blog_service, "generate_one", fake_generate_one)
|
|
|
|
h = await auth_headers("bloggen10")
|
|
place_id = await _place(client, h, name="이력펜션")
|
|
async with db_engine.begin() as conn:
|
|
await conn.execute(
|
|
text("INSERT INTO sites (site_id, place_id, status) VALUES (:sid, :pid, :st)"),
|
|
{"sid": uuid.uuid4(), "pid": place_id, "st": SiteStatus.PUBLISHED.value},
|
|
)
|
|
start = date.today()
|
|
generate_res = await client.post(
|
|
f"/v1/place/{place_id}/post/generate", headers=h,
|
|
params={"start": start.isoformat(), "end": (start + timedelta(days=29)).isoformat()},
|
|
)
|
|
created = generate_res.json()["created"]
|
|
assert created > 0
|
|
|
|
res = await client.get(f"/v1/place/{place_id}/post/history", headers=h)
|
|
|
|
batches = res.json()["batches"]
|
|
assert len(batches) == 1
|
|
assert batches[0]["count"] == created
|
|
assert batches[0]["model"] == "gemini-test-model"
|
|
|
|
|
|
async def test_mail_has_one_click_approve_and_autologin_edit_links(client, db_engine, auth_headers, monkeypatch):
|
|
"""사장님 지시: "승인이랑 수정하기 있어야해" — 승인은 토큰 링크 하나, 수정은
|
|
그날짜리 자동 로그인 토큰을 실은 빌더 앱 링크."""
|
|
from services import blog_jobs, mail_service
|
|
|
|
monkeypatch.setattr(mail_service, "is_configured", lambda: True)
|
|
sent_calls = []
|
|
monkeypatch.setattr(mail_service, "send", lambda **kwargs: sent_calls.append(kwargs) or True)
|
|
|
|
h = await auth_headers("bloggen11")
|
|
place_id = await _place(client, h, name="메일링크펜션")
|
|
async with db_engine.begin() as conn:
|
|
await conn.execute(
|
|
text("INSERT INTO sites (site_id, place_id, status) VALUES (:sid, :pid, :st)"),
|
|
{"sid": uuid.uuid4(), "pid": place_id, "st": SiteStatus.PUBLISHED.value},
|
|
)
|
|
await conn.execute(
|
|
text("UPDATE users SET email = :e WHERE id = :id"), {"e": "owner@example.com", "id": "bloggen11"},
|
|
)
|
|
post_id = await _seed_post(db_engine, place_id, status=PostStatus.REVIEWED, scheduled=date.today())
|
|
|
|
sent = await blog_jobs.send_reviewed()
|
|
|
|
assert sent == 1
|
|
mail_text = sent_calls[0]["text"]
|
|
assert "/v1/site/post/approve?t=" in mail_text
|
|
assert "/blog?placeId=" in mail_text
|
|
assert f"postId={post_id}" in mail_text
|
|
assert "auto=" in mail_text
|
|
|
|
|
|
async def test_generate_one_fills_a_specific_empty_date(client, db_engine, auth_headers, monkeypatch):
|
|
"""사장님 지시: "개별적으로 새로 만들수있게 해줘" — 달력에서 빈 날짜 하나만 콕 집어 채운다."""
|
|
from services import blog_service
|
|
|
|
async def fake_generate_one(*, place_name, region, topic_kind, material, used_topics):
|
|
return ("테스트로 만든 문구입니다. " + BODY, "gemini-test-model")
|
|
|
|
monkeypatch.setattr(blog_service, "generate_one", fake_generate_one)
|
|
|
|
h = await auth_headers("bloggen12")
|
|
place_id = await _place(client, h, name="개별생성펜션")
|
|
async with db_engine.begin() as conn:
|
|
await conn.execute(
|
|
text("INSERT INTO sites (site_id, place_id, status) VALUES (:sid, :pid, :st)"),
|
|
{"sid": uuid.uuid4(), "pid": place_id, "st": SiteStatus.PUBLISHED.value},
|
|
)
|
|
target = date.today() + timedelta(days=3)
|
|
|
|
res = await client.post(
|
|
f"/v1/place/{place_id}/post/generate-one", headers=h, params={"date": target.isoformat()},
|
|
)
|
|
|
|
body = res.json()
|
|
assert body["result"]["success"] is True
|
|
assert body["post"]["scheduled_date"] == target.isoformat()
|
|
assert await _status(db_engine, body["post"]["post_id"]) == PostStatus.REVIEWED.value
|
|
|
|
|
|
async def test_generate_one_fails_when_date_already_taken(client, db_engine, auth_headers, monkeypatch):
|
|
"""이미 그 날짜에 글이 있으면(유니크 충돌) 조용히 덮지 않고 실패로 답한다."""
|
|
from services import blog_service
|
|
|
|
async def fake_generate_one(*, place_name, region, topic_kind, material, used_topics):
|
|
return ("테스트로 만든 문구입니다. " + BODY, "gemini-test-model")
|
|
|
|
monkeypatch.setattr(blog_service, "generate_one", fake_generate_one)
|
|
|
|
h = await auth_headers("bloggen13")
|
|
place_id = await _place(client, h, name="중복날짜펜션")
|
|
async with db_engine.begin() as conn:
|
|
await conn.execute(
|
|
text("INSERT INTO sites (site_id, place_id, status) VALUES (:sid, :pid, :st)"),
|
|
{"sid": uuid.uuid4(), "pid": place_id, "st": SiteStatus.PUBLISHED.value},
|
|
)
|
|
target = date.today() + timedelta(days=3)
|
|
await _seed_post(db_engine, place_id, scheduled=target)
|
|
|
|
res = await client.post(
|
|
f"/v1/place/{place_id}/post/generate-one", headers=h, params={"date": target.isoformat()},
|
|
)
|
|
|
|
body = res.json()
|
|
assert body["result"]["success"] is False
|
|
assert body.get("post") is None
|
|
|
|
|
|
async def test_generate_one_is_scoped_to_owner(client, db_engine, auth_headers):
|
|
owner = await auth_headers("bloggen14")
|
|
other = await auth_headers("bloggen15")
|
|
place_id = await _place(client, owner, name="타인개별생성펜션")
|
|
|
|
res = await client.post(
|
|
f"/v1/place/{place_id}/post/generate-one", headers=other,
|
|
params={"date": date.today().isoformat()},
|
|
)
|
|
|
|
assert res.json()["result"]["success"] is False
|