Skip to content

[Fix] 노션 url 분석 시 selenium timeout으로 분석 실패 케이스 개선 - #142

Merged
hwiyoon20010309 merged 3 commits into
mainfrom
fix/notion-selenium-timeout-retry
Aug 12, 2026
Merged

hwiyoon20010309 merged 3 commits into
mainfrom
fix/notion-selenium-timeout-retry

Conversation

@m00nsh

@m00nsh m00nsh commented Aug 12, 2026 •

Copy link
Copy Markdown
Collaborator

✨ 어떤 이유로 PR를 하셨나요?

  • feature 병합
  • 버그 수정(아래에 issue #를 남겨주세요)
  • 코드 개선
  • 코드 수정
  • 배포
  • 기타(아래에 자세한 내용 기입해주세요)

📋 세부 내용 - 왜 해당 PR이 필요한지 작업 내용을 자세하게 설명해주세요

변경 사항

1. Notion Selenium 크롤링 안정화

  • Notion 페이지에 PageLoadStrategy.EAGER 적용
  • Notion 전용 page load timeout 조정
  • Notion 전용 script timeout 조정
  • TimeoutException, ScriptTimeoutException 발생 시 새 ChromeDriver 세션으로 1회 retry
  • 각 attempt 종료 시 driver.quit() 수행
  • 기존 일반 WEB 및 다른 플랫폼의 Selenium 정책은 유지

2. Selenium child process zombie 누적 방지

  • docker-compose.yml의 application service에 init: true 적용
  • 컨테이너 PID 1이 child process를 reap할 수 있도록 구성

3. 로그인/권한이 필요한 외부 페이지 감지

  • MATERIAL_SOURCE_AUTH_REQUIRED (MATERIAL4032) 오류 추가
  • HTTP 401/403 응답을 일반 크롤링 실패와 구분
  • login/signin/auth/oauth/sso 경로 감지
  • password form + 인증 관련 signal 조합 감지
  • Notion의 명시적 private/access denied 페이지 감지
  • 보호된 페이지는 분석 및 색인 단계로 진행하지 않도록 처리

4. 오탐 방지

  • 본문에 login, OAuth, 회원가입 등의 단어가 있다는 이유만으로 차단하지 않음
  • 일반 기술 문서의 auth 관련 slug/text는 정상 분석되도록 유지
  • 기존 API DTO / 응답 구조 / DB schema는 변경하지 않음

테스트

  • .\gradlew.bat compileTestJava
  • 관련 HTML fetch/render/extractor 집중 테스트
  • .\gradlew.bat test
  • .\gradlew.bat --no-daemon clean build -x test
  • git diff --check

모두 통과

로컬 Swagger Notion 회귀 테스트

기존 테스트용 공개 Notion URL 4개를 각각 반복 분석하여 정상 분석 확인

  • shinmini.notion.site → 성공
  • sosoeasyvote.notion.site → 성공
  • jscode.notion.site → 성공
  • makeus-challenge.notion.site → 성공

기존에 반복적으로 발생하던 MATERIAL5022 / Selenium page load timeout 재현되지 않음

참고

HTTP 200으로 서비스 공통 landing/shell 페이지를 반환하는 일부 인증 우회 케이스는
사이트별 응답 형태가 달라 일반적인 로그인 필요 감지로 완전히 판별하기 어려울 수 있습니다.

이번 PR에서는 명확한 인증/권한 신호를 안전하게 구분하는 범위까지만 적용했습니다.

Nginx request timeout 조정은 애플리케이션 코드와 별개의 서버 설정이므로
배포 후 별도 적용 예정입니다.

📸 작업 화면 스크린샷

⚠️ PR하기 전에 확인해주세요

  • 로컬테스트를 진행하셨나요?
  • 머지할 브랜치를 확인하셨나요?
  • 관련 label을 선택하셨나요?

🚨 관련 이슈 번호 [ ]

Summary by CodeRabbit

  • 개선 사항
    • 로그인이 필요하거나 접근이 제한된 웹 자료를 더 정확히 감지합니다.
    • 인증이 필요한 자료에는 명확한 오류 안내를 제공하고, 일반 문서의 로그인 관련 단어는 오탐으로 처리하지 않습니다.
    • Notion 자료 로딩의 안정성과 재시도 처리가 개선되었습니다.
  • 버그 수정
    • HTTP 401·403 응답 및 비공개 페이지를 콘텐츠 추출 실패가 아닌 인증 필요 오류로 구분합니다.
    • 애플리케이션 종료 시 프로세스 정리 동작이 개선되었습니다.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@m00nsh, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 34 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f5352ff8-cb3e-4dea-85d5-23c661bc449a

📥 Commits

Reviewing files that changed from the base of the PR and between f24da29 and 4dec04b.

📒 Files selected for processing (8)
  • src/main/java/com/teaching/backend/domain/material/service/extract/CafeMaterialContentExtractor.java
  • src/main/java/com/teaching/backend/domain/material/service/extract/ExternalHtmlDocumentClient.java
  • src/main/java/com/teaching/backend/domain/material/service/extract/NotionMaterialContentExtractor.java
  • src/main/java/com/teaching/backend/domain/material/service/extract/ProtectedSourceDetector.java
  • src/test/java/com/teaching/backend/domain/material/service/extract/ExternalHtmlDocumentClientTest.java
  • src/test/java/com/teaching/backend/domain/material/service/extract/HtmlMaterialContentExtractorTest.java
  • src/test/java/com/teaching/backend/domain/material/service/extract/ProtectedSourceDetectorTest.java
  • src/test/java/com/teaching/backend/domain/material/service/extract/RenderedHtmlDocumentClientTest.java
📝 Walkthrough

Walkthrough

자료 추출기가 HTTP 상태, HTML 본문, 렌더링 결과의 인증 보호 신호를 감지합니다. 인증이 필요하면 MATERIAL_SOURCE_AUTH_REQUIRED 오류를 반환합니다. Notion 렌더링에는 별도 전략과 타임아웃 재시도를 적용합니다. app 컨테이너에는 초기화 프로세스를 추가합니다.

Changes

자료 원문 인증 보호 처리

Layer / File(s) Summary
보호 원문 판별 계약
src/main/java/com/teaching/backend/domain/material/exception/MaterialErrorCode.java, src/main/java/com/teaching/backend/domain/material/service/extract/ProtectedSourceDetector.java, src/test/java/com/teaching/backend/domain/material/service/extract/ProtectedSourceDetectorTest.java
MATERIAL_SOURCE_AUTH_REQUIRED 오류 코드와 인증 호스트, 경로, HTML 신호 판별 규칙을 추가했습니다. 공개 Notion 문서와 일반 기술 문서의 오탐 방지 동작을 테스트합니다.
외부 HTML 인증 검증
src/main/java/com/teaching/backend/domain/material/service/extract/*HtmlMaterialContentExtractor.java, src/main/java/com/teaching/backend/domain/material/service/extract/ExternalHtmlDocumentClient.java, src/test/java/com/teaching/backend/domain/material/service/extract/ExternalHtmlDocumentClientTest.java, src/test/java/com/teaching/backend/domain/material/service/extract/HtmlMaterialContentExtractorTest.java
HTTP 401·403 응답과 보호 HTML을 인증 필요 오류로 처리합니다. Generic, Cafe, Notion 추출기의 문서 검증과 오류 코드를 갱신했습니다.
렌더링 HTML 처리와 재시도
src/main/java/com/teaching/backend/domain/material/service/extract/RenderedHtmlDocumentClient.java, src/test/java/com/teaching/backend/domain/material/service/extract/RenderedHtmlDocumentClientTest.java
Notion URL에 EAGER 로드 전략과 전용 타임아웃을 적용합니다. 타임아웃은 최대 한 번 재시도하고, 인증 URL과 보호 HTML은 즉시 거부합니다.

컨테이너 초기화 설정

Layer / File(s) Summary
app 컨테이너 초기화
docker-compose.yml
app 서비스에 init: true를 추가했습니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • TeachING-Dev/Backend#79: 자료 추출기와 Selenium 기반 HTML 검색 경로를 함께 수정했습니다.
  • TeachING-Dev/Backend#107: HTML 추출기와 렌더링 문서 클라이언트의 검증 동작을 함께 확장했습니다.

Suggested reviewers: mymyseoyoung

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ExternalHtmlDocumentClient
  participant RenderedHtmlDocumentClient
  participant ProtectedSourceDetector
  Client->>ExternalHtmlDocumentClient: HTML 원문 요청
  ExternalHtmlDocumentClient->>ProtectedSourceDetector: 응답 상태와 HTML 보호 여부 확인
  ProtectedSourceDetector-->>ExternalHtmlDocumentClient: 공개 원문 또는 인증 필요 오류
  Client->>RenderedHtmlDocumentClient: 렌더링 원문 요청
  RenderedHtmlDocumentClient->>ProtectedSourceDetector: 최종 URL과 렌더링 HTML 확인
  ProtectedSourceDetector-->>RenderedHtmlDocumentClient: 공개 원문 또는 인증 필요 오류
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 Notion Selenium 안정화와 보호된 소스 URL 감지라는 PR의 주요 변경 사항을 정확하고 간결하게 설명합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/notion-selenium-timeout-retry

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (7)
src/main/java/com/teaching/backend/domain/material/service/extract/GenericWebMaterialContentExtractor.java (1)

89-93: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

보호 문서 검증 로직이 AbstractHtmlMaterialContentExtractor와 중복됩니다.

AbstractHtmlMaterialContentExtractor.validateDocument(Line 119-126)는 동일한 검사를 수행합니다. 보안 판정 로직이 두 곳에 존재하면 한쪽만 수정되어 판정 기준이 달라질 수 있습니다.

ProtectedSourceDetector에 validateOrThrow(HtmlDocument) 같은 공용 메서드를 추가하고 두 곳에서 호출하도록 정리하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/teaching/backend/domain/material/service/extract/GenericWebMaterialContentExtractor.java`
around lines 89 - 93, 중복된 보호 문서 판정 로직을 제거하세요. ProtectedSourceDetector에
HtmlDocument를 받아 보호 문서인 경우 MATERIAL_SOURCE_AUTH_REQUIRED를 발생시키는 공용
validateOrThrow 메서드를 추가하고, GenericWebMaterialContentExtractor.validateDocument와
AbstractHtmlMaterialContentExtractor.validateDocument가 해당 메서드를 호출하도록 변경하세요.
src/main/java/com/teaching/backend/domain/material/service/extract/NotionMaterialContentExtractor.java (1)

33-35: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

이 문구 검사는 이제 중복입니다.

ProtectedSourceDetector.hasNotionPrivateSignal은 대소문자를 구분하지 않고 "this page is private"와 "you do not have access"를 이미 검사합니다. super.validateDocument가 먼저 실행되므로 이 블록은 도달하지 않습니다. 중복 판정 기준을 한 곳으로 모으려면 이 블록을 제거하는 방안을 검토하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/teaching/backend/domain/material/service/extract/NotionMaterialContentExtractor.java`
around lines 33 - 35, Remove the duplicate private-page message check in
NotionMaterialContentExtractor, including its local MaterialErrorCode exception,
and rely on ProtectedSourceDetector.hasNotionPrivateSignal through the existing
super.validateDocument flow for case-insensitive detection.
src/main/java/com/teaching/backend/domain/material/service/extract/RenderedHtmlDocumentClient.java (3)

194-199: 🩺 Stability & Availability | 🔵 Trivial

Notion 렌더링의 최대 소요 시간을 확인하세요.

Notion URL은 페이지 로드 타임아웃 30초에 최대 2회 시도를 수행합니다. 여기에 waitUntilReadable의 대기 시간과 NOTION_SCROLL_STEPS(8) × NOTION_SCROLL_WAIT_MS(250ms) 스크롤 시간이 더해집니다. 최악의 경우 하나의 요청이 60초 이상 렌더 퍼밋을 점유합니다. max-concurrency 기본값이 2이므로, 동시 요청 2건이 장기 점유하면 나머지 요청은 acquire-timeout-ms(500ms) 후 즉시 폴백 실패로 처리됩니다.

전체 렌더 예산 상한을 두거나, Nginx·게이트웨이 타임아웃과 함께 값을 정하세요. PR 설명의 Nginx 타임아웃 변경 계획과 순서를 맞추는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/teaching/backend/domain/material/service/extract/RenderedHtmlDocumentClient.java`
around lines 194 - 199, Update the Notion rendering flow around renderAttempt
and its retry settings to enforce a bounded total render-time budget, including
page-load, waitUntilReadable, and scrolling delays across all attempts. Size the
budget consistently with the planned Nginx or gateway timeout so long-running
renders do not monopolize render permits and trigger immediate fallback failures
for queued requests.

76-76: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Notion 타임아웃 기본값이 세 곳에 하드코딩되어 있습니다.

30000과 10000이 세 생성자 위임 경로에 반복됩니다. 값 하나를 변경하면 나머지 두 곳이 누락될 수 있습니다. private static final long DEFAULT_NOTION_PAGE_LOAD_TIMEOUT_MS = 30_000L;와 DEFAULT_NOTION_SCRIPT_TIMEOUT_MS = 10_000L; 상수를 선언하고 참조하세요. @Value 기본값과의 일치 여부도 함께 유지할 수 있습니다.

Also applies to: 93-94, 143-144

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/teaching/backend/domain/material/service/extract/RenderedHtmlDocumentClient.java`
at line 76, RenderedHtmlDocumentClient의 생성자 위임 경로에 반복된 Notion 페이지 로드 및 스크립트 타임아웃
리터럴을 제거하세요. 클래스에 DEFAULT_NOTION_PAGE_LOAD_TIMEOUT_MS와
DEFAULT_NOTION_SCRIPT_TIMEOUT_MS 상수를 선언하고 해당 생성자 호출부(76, 93-94, 143-144)에서 참조하며,
`@Value` 기본값과 동일한 값을 유지하세요.

200-239: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

타임아웃 예외 처리를 하나의 블록으로 통합하세요.

ScriptTimeoutException과 TimeoutException은 WebDriverException의 서로 다른 하위 클래스입니다. 따라서 ScriptTimeoutException은 후속 catch (RuntimeException e)에서 처리됩니다.

두 catch 블록의 재시도·로깅·실패 처리는 중복됩니다. MaterialException 처리 이후의 catch (RuntimeException e) 하나로 통합하세요. 현재 구조에서는 isRetryableTimeout의 TimeoutException 검사만 도달할 수 없으며, 통합 후에는 두 예외 검사를 유지해야 합니다. 동일한 정리를 320-322행에도 적용하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/teaching/backend/domain/material/service/extract/RenderedHtmlDocumentClient.java`
around lines 200 - 239, Remove the dedicated TimeoutException catch block in the
rendered HTML fallback flow and consolidate its retry, logging, and
terminal-failure handling into the RuntimeException catch after
MaterialException. Update isRetryableTimeout to recognize both TimeoutException
and ScriptTimeoutException so both timeout subclasses remain retryable; apply
the same consolidation at the corresponding handling around the second
occurrence.
src/main/java/com/teaching/backend/domain/material/service/extract/AbstractHtmlMaterialContentExtractor.java (1)

119-126: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

MATERIAL_SOURCE_AUTH_REQUIRED가 플랫폼 폴백 경로에서 가려질 수 있습니다.

validateDocument는 이제 MaterialException을 던집니다. tryPlatformFallback은 MaterialException을 잡아 Optional.empty()로 변환합니다. 따라서 CafeMaterialContentExtractor.parsePlatformFallback이 iframe 문서에 대해 인증 필요 예외를 던지면, 그 예외는 소실되고 렌더링 폴백을 거쳐 최종적으로 MATERIAL_CONTENT_EMPTY가 반환됩니다. 사용자는 "로그인 필요" 대신 "본문 비어 있음" 오류를 받습니다.

인증 필요 오류는 폴백 대상에서 제외하는 방안을 검토하세요.

♻️ 제안 리팩터
    private Optional<ParsedHtmlContent> tryPlatformFallback(
            String originalUrl,
            HtmlDocument initialDocument
    ) {
        try {
            return parsePlatformFallback(originalUrl, initialDocument);
        } catch (MaterialException e) {
            if (e.getErrorCode() == MaterialErrorCode.MATERIAL_SOURCE_AUTH_REQUIRED) {
                throw e;
            }
            // 기존 로깅 유지
            return Optional.empty();
        }
    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/teaching/backend/domain/material/service/extract/AbstractHtmlMaterialContentExtractor.java`
around lines 119 - 126, Update tryPlatformFallback to rethrow MaterialException
when its error code is MATERIAL_SOURCE_AUTH_REQUIRED, while preserving the
existing Optional.empty() conversion and logging for other MaterialException
values. This ensures parsePlatformFallback authentication failures remain
visible instead of reaching the rendering fallback as MATERIAL_CONTENT_EMPTY.
src/main/java/com/teaching/backend/domain/material/service/extract/ProtectedSourceDetector.java (1)

159-162: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

hasNotionPrivateSignal은 모든 플랫폼 문서에 적용됩니다.

메서드 이름은 Notion 전용을 뜻하지만 isProtectedHtml은 URL 조건 없이 이 검사를 실행합니다. 본문 어디에든 "this page is private" 또는 "you do not have access" 문자열이 있으면 문서 전체가 거부됩니다. 예를 들어 접근 제어를 설명하는 기술 문서가 이 문구를 인용하면 오탐이 발생합니다.

문구 검사를 Notion 호스트로 한정하거나, 제목·헤딩 요소로 범위를 좁히는 방법을 검토하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/teaching/backend/domain/material/service/extract/ProtectedSourceDetector.java`
around lines 159 - 162, Update the protected-document detection flow around
hasNotionPrivateSignal and isProtectedHtml so the Notion private-page phrases
are evaluated only for Notion hosts or within title/heading elements, rather
than arbitrary document body text. Preserve the existing protection behavior for
genuine Notion private-page responses while preventing quoted phrases in
unrelated platform content from rejecting the document.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/com/teaching/backend/domain/material/service/extract/CafeMaterialContentExtractor.java`:
- Around line 36-37: Update validateDocument in
src/main/java/com/teaching/backend/domain/material/service/extract/CafeMaterialContentExtractor.java
at lines 36-37 and
src/main/java/com/teaching/backend/domain/material/service/extract/NotionMaterialContentExtractor.java
at lines 28-29: after each super.validateDocument(document) call, return
immediately when document is null before accessing document.body().

In
`@src/main/java/com/teaching/backend/domain/material/service/extract/ExternalHtmlDocumentClient.java`:
- Around line 108-113: Update the authentication-status handling in
ExternalHtmlDocumentClient so only responses that can be confidently identified
as authentication-required produce MATERIAL_SOURCE_AUTH_REQUIRED; do not
classify every 403 response this way, allowing bot, regional, and WAF blocks to
follow the generic failure path. Ensure the 401/403 handling also invokes
logExtractionFailure with at least the request host and status code before
returning or propagating the error.

In
`@src/main/java/com/teaching/backend/domain/material/service/extract/ProtectedSourceDetector.java`:
- Around line 13-19: Remove "github.com" from the AUTH_HOSTS list in
ProtectedSourceDetector, leaving the other authentication-only hosts unchanged.
Preserve GitHub login and signin detection through the existing
hasAuthPathSegment logic.

In
`@src/test/java/com/teaching/backend/domain/material/service/extract/RenderedHtmlDocumentClientTest.java`:
- Around line 531-543: Update the TestRenderedHtmlDocumentClient constructor to
delegate to the superclass overload that accepts the provided permits Semaphore,
rather than the overload that creates a new semaphore from maxConcurrency.
Ensure the permits argument is passed through so the related strategy tests
validate the semaphore they construct.

---

Nitpick comments:
In
`@src/main/java/com/teaching/backend/domain/material/service/extract/AbstractHtmlMaterialContentExtractor.java`:
- Around line 119-126: Update tryPlatformFallback to rethrow MaterialException
when its error code is MATERIAL_SOURCE_AUTH_REQUIRED, while preserving the
existing Optional.empty() conversion and logging for other MaterialException
values. This ensures parsePlatformFallback authentication failures remain
visible instead of reaching the rendering fallback as MATERIAL_CONTENT_EMPTY.

In
`@src/main/java/com/teaching/backend/domain/material/service/extract/GenericWebMaterialContentExtractor.java`:
- Around line 89-93: 중복된 보호 문서 판정 로직을 제거하세요. ProtectedSourceDetector에
HtmlDocument를 받아 보호 문서인 경우 MATERIAL_SOURCE_AUTH_REQUIRED를 발생시키는 공용
validateOrThrow 메서드를 추가하고, GenericWebMaterialContentExtractor.validateDocument와
AbstractHtmlMaterialContentExtractor.validateDocument가 해당 메서드를 호출하도록 변경하세요.

In
`@src/main/java/com/teaching/backend/domain/material/service/extract/NotionMaterialContentExtractor.java`:
- Around line 33-35: Remove the duplicate private-page message check in
NotionMaterialContentExtractor, including its local MaterialErrorCode exception,
and rely on ProtectedSourceDetector.hasNotionPrivateSignal through the existing
super.validateDocument flow for case-insensitive detection.

In
`@src/main/java/com/teaching/backend/domain/material/service/extract/ProtectedSourceDetector.java`:
- Around line 159-162: Update the protected-document detection flow around
hasNotionPrivateSignal and isProtectedHtml so the Notion private-page phrases
are evaluated only for Notion hosts or within title/heading elements, rather
than arbitrary document body text. Preserve the existing protection behavior for
genuine Notion private-page responses while preventing quoted phrases in
unrelated platform content from rejecting the document.

In
`@src/main/java/com/teaching/backend/domain/material/service/extract/RenderedHtmlDocumentClient.java`:
- Around line 194-199: Update the Notion rendering flow around renderAttempt and
its retry settings to enforce a bounded total render-time budget, including
page-load, waitUntilReadable, and scrolling delays across all attempts. Size the
budget consistently with the planned Nginx or gateway timeout so long-running
renders do not monopolize render permits and trigger immediate fallback failures
for queued requests.
- Line 76: RenderedHtmlDocumentClient의 생성자 위임 경로에 반복된 Notion 페이지 로드 및 스크립트 타임아웃
리터럴을 제거하세요. 클래스에 DEFAULT_NOTION_PAGE_LOAD_TIMEOUT_MS와
DEFAULT_NOTION_SCRIPT_TIMEOUT_MS 상수를 선언하고 해당 생성자 호출부(76, 93-94, 143-144)에서 참조하며,
`@Value` 기본값과 동일한 값을 유지하세요.
- Around line 200-239: Remove the dedicated TimeoutException catch block in the
rendered HTML fallback flow and consolidate its retry, logging, and
terminal-failure handling into the RuntimeException catch after
MaterialException. Update isRetryableTimeout to recognize both TimeoutException
and ScriptTimeoutException so both timeout subclasses remain retryable; apply
the same consolidation at the corresponding handling around the second
occurrence.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 274c9064-cc9a-4839-a97c-2a5353aa0033

📥 Commits

Reviewing files that changed from the base of the PR and between 2655b9e and f24da29.

📒 Files selected for processing (13)
  • docker-compose.yml
  • src/main/java/com/teaching/backend/domain/material/exception/MaterialErrorCode.java
  • src/main/java/com/teaching/backend/domain/material/service/extract/AbstractHtmlMaterialContentExtractor.java
  • src/main/java/com/teaching/backend/domain/material/service/extract/CafeMaterialContentExtractor.java
  • src/main/java/com/teaching/backend/domain/material/service/extract/ExternalHtmlDocumentClient.java
  • src/main/java/com/teaching/backend/domain/material/service/extract/GenericWebMaterialContentExtractor.java
  • src/main/java/com/teaching/backend/domain/material/service/extract/NotionMaterialContentExtractor.java
  • src/main/java/com/teaching/backend/domain/material/service/extract/ProtectedSourceDetector.java
  • src/main/java/com/teaching/backend/domain/material/service/extract/RenderedHtmlDocumentClient.java
  • src/test/java/com/teaching/backend/domain/material/service/extract/ExternalHtmlDocumentClientTest.java
  • src/test/java/com/teaching/backend/domain/material/service/extract/HtmlMaterialContentExtractorTest.java
  • src/test/java/com/teaching/backend/domain/material/service/extract/ProtectedSourceDetectorTest.java
  • src/test/java/com/teaching/backend/domain/material/service/extract/RenderedHtmlDocumentClientTest.java

Comment on lines +108 to +113
if (isAuthenticationRequiredStatus(statusCode)) {
return response.releaseBody()
.then(Mono.error(new MaterialException(
MaterialErrorCode.MATERIAL_SOURCE_AUTH_REQUIRED
)));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

403 응답을 모두 인증 필요로 분류하면 오분류가 발생합니다.

403은 인증 요구 외에도 봇 차단, 지역 차단, WAF 규칙에서 자주 사용됩니다. 이 경로는 이제 그러한 응답에도 "로그인 또는 회원가입이 필요한 페이지"라는 메시지를 반환합니다.

또한 이 분기는 logExtractionFailure를 호출하지 않습니다. 401·403 실패는 로그에 남지 않으므로 원인 분석이 어려워집니다. 최소한 호스트와 상태 코드를 기록하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/teaching/backend/domain/material/service/extract/ExternalHtmlDocumentClient.java`
around lines 108 - 113, Update the authentication-status handling in
ExternalHtmlDocumentClient so only responses that can be confidently identified
as authentication-required produce MATERIAL_SOURCE_AUTH_REQUIRED; do not
classify every 403 response this way, allowing bot, regional, and WAF blocks to
follow the generic failure path. Ensure the 401/403 handling also invokes
logExtractionFailure with at least the request host and status code before
returning or propagating the error.

@hwiyoon20010309
hwiyoon20010309 merged commit 9d54796 into main Aug 12, 2026
2 checks passed
@m00nsh
m00nsh deleted the fix/notion-selenium-timeout-retry branch August 13, 2026 12:13
@m00nsh m00nsh added the enhancement New feature or request label Aug 13, 2026
@m00nsh m00nsh changed the title [Fix] stabilize notion selenium timeout and detect protected source urls [Fix] 노션 url 분석 시 selenium timeout으로 분석 실패하는 케이스 개선 Aug 13, 2026
@m00nsh m00nsh changed the title [Fix] 노션 url 분석 시 selenium timeout으로 분석 실패하는 케이스 개선 [Fix] 노션 url 분석 시 selenium timeout으로 분석 실패 케이스 개선 Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants