mirror of
https://github.com/NomaDamas/k-skill.git
synced 2026-06-24 02:04:11 +00:00
fix(startup-support): remove fabricated detail data
This commit is contained in:
parent
9b2e0957f2
commit
cff6b29ff9
7 changed files with 165 additions and 199 deletions
|
|
@ -85,7 +85,7 @@ Claude Code, Codex, OpenCode, OpenClaw/ClawHub 등 각종 코딩 에이전트
|
||||||
| ~~근처 블루리본 맛집~~ ⚠️ 지원 중단 | ~~`blue-ribbon-nearby`~~ | ~~현재 위치 기준 근처 블루리본 선정 맛집 조회~~ | ~~불필요~~ | ~~[근처 블루리본 맛집 가이드](docs/features/blue-ribbon-nearby.md)~~ |
|
| ~~근처 블루리본 맛집~~ ⚠️ 지원 중단 | ~~`blue-ribbon-nearby`~~ | ~~현재 위치 기준 근처 블루리본 선정 맛집 조회~~ | ~~불필요~~ | ~~[근처 블루리본 맛집 가이드](docs/features/blue-ribbon-nearby.md)~~ |
|
||||||
| 근처 술집 조회 | `kakao-bar-nearby` | 현재 위치 기준 영업 상태·메뉴·좌석·전화번호가 포함된 근처 술집 조회 | 불필요 | [근처 술집 조회 가이드](docs/features/kakao-bar-nearby.md) |
|
| 근처 술집 조회 | `kakao-bar-nearby` | 현재 위치 기준 영업 상태·메뉴·좌석·전화번호가 포함된 근처 술집 조회 | 불필요 | [근처 술집 조회 가이드](docs/features/kakao-bar-nearby.md) |
|
||||||
| 우편번호 검색 | `zipcode-search` | 주소 키워드로 우편번호 + 공식 영문주소 조회 | 불필요 | [우편번호 검색 가이드](docs/features/zipcode-search.md) |
|
| 우편번호 검색 | `zipcode-search` | 주소 키워드로 우편번호 + 공식 영문주소 조회 | 불필요 | [우편번호 검색 가이드](docs/features/zipcode-search.md) |
|
||||||
| startup-support | Search Korean government startup support programs, grants, and subsidies for startups, SMEs, and entrepreneurs. Use when users ask about 창업 지원, 스타트업 지원금, 중소기업 지원, 정부 지원사업. | No login | [docs/features/startup-support.md](https://github.com/k-skill/k-skill/blob/main/docs/features/startup-support.md) |
|
| 창업 지원사업 조회 | `startup-support` | 정부·지자체 스타트업 지원사업 목록 조회 (상세 조회는 원본 공고 링크로 확인) | 불필요 | [창업 지원사업 조회 가이드](docs/features/startup-support.md) |
|
||||||
| 다이소 상품 조회 | `daiso-product-search` | 다이소 매장별 상품 픽업 가능 여부 확인 (정확한 매장별 재고 수량은 다이소몰 보안 정책으로 2026-05-05 부터 차단됨) | 불필요 | [다이소 상품 조회 가이드](docs/features/daiso-product-search.md) |
|
| 다이소 상품 조회 | `daiso-product-search` | 다이소 매장별 상품 픽업 가능 여부 확인 (정확한 매장별 재고 수량은 다이소몰 보안 정책으로 2026-05-05 부터 차단됨) | 불필요 | [다이소 상품 조회 가이드](docs/features/daiso-product-search.md) |
|
||||||
| 강남언니 병원 조회 | `gangnamunni-clinic-search` | 강남언니 공개 검색 페이지에서 성형외과·피부과 병원 후보, 평점, 리뷰 수, 지원 언어, 공개 링크 조회 | 불필요 | [강남언니 병원 조회 가이드](docs/features/gangnamunni-clinic-search.md) |
|
| 강남언니 병원 조회 | `gangnamunni-clinic-search` | 강남언니 공개 검색 페이지에서 성형외과·피부과 병원 후보, 평점, 리뷰 수, 지원 언어, 공개 링크 조회 | 불필요 | [강남언니 병원 조회 가이드](docs/features/gangnamunni-clinic-search.md) |
|
||||||
| 마켓컬리 상품 조회 | `market-kurly-search` | 마켓컬리 상품 검색, 현재 가격, 할인 여부, 품절 여부 조회 | 불필요 | [마켓컬리 상품 조회 가이드](docs/features/market-kurly-search.md) |
|
| 마켓컬리 상품 조회 | `market-kurly-search` | 마켓컬리 상품 검색, 현재 가격, 할인 여부, 품절 여부 조회 | 불필요 | [마켓컬리 상품 조회 가이드](docs/features/market-kurly-search.md) |
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,7 @@
|
||||||
|
|
||||||
#### k-skill-proxy 라우트
|
#### k-skill-proxy 라우트
|
||||||
|
|
||||||
- `GET /v1/startup-support/list`: 지원사업 목록 조회
|
공공데이터포털 K-Startup OpenAPI는 별도 `kstartup-search` 스킬과 `k-skill-proxy`의 `/v1/kstartup/*` 라우트가 담당합니다. `startup-support` helper는 지역별 공개 API 목록을 조회하고, 상세 정보는 결과의 공식 `url` 로 확인합니다.
|
||||||
- `GET /v1/startup-support/detail/:program_id`: 특정 지원사업 상세 정보
|
|
||||||
- `GET /v1/startup-support/region/:region`: 특정 지역 지원사업 조회
|
|
||||||
- `GET /v1/startup-support/deadline`: 임박 마감 지원사업
|
|
||||||
|
|
||||||
#### Python 스크립트
|
#### Python 스크립트
|
||||||
|
|
||||||
|
|
@ -63,8 +60,7 @@ keyword_programs = search_startup_support(keyword='청년')
|
||||||
# 마감 임박 검색
|
# 마감 임박 검색
|
||||||
deadline_programs = search_startup_support(deadline_only=True)
|
deadline_programs = search_startup_support(deadline_only=True)
|
||||||
|
|
||||||
# 상세 정보 조회
|
# 상세 정보는 목록 결과의 공식 url로 확인
|
||||||
detail = get_startup_program_detail('test_001')
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 데이터 소스
|
## 데이터 소스
|
||||||
|
|
@ -72,7 +68,7 @@ detail = get_startup_program_detail('test_001')
|
||||||
### 1. 공공데이터포털
|
### 1. 공공데이터포털
|
||||||
- **기관**: 중소벤처기업부
|
- **기관**: 중소벤처기업부
|
||||||
- **API**: 스타트업 지원사업 정보
|
- **API**: 스타트업 지원사업 정보
|
||||||
- **인증**: API 키 필요 (DATA_GO_KR_API_KEY)
|
- **인증**: hosted/self-host proxy 운영 서버에서 API 키 주입
|
||||||
|
|
||||||
### 2. 지자체별 사이트
|
### 2. 지자체별 사이트
|
||||||
- **서울시**: https://seoulstartup.go.kr
|
- **서울시**: https://seoulstartup.go.kr
|
||||||
|
|
|
||||||
|
|
@ -428,9 +428,7 @@ node scripts/korean_character_count.js --text $'첫 줄\n둘째 줄🙂' --profi
|
||||||
startup-support 스킬은 다음과 같은 환경이 필요합니다:
|
startup-support 스킬은 다음과 같은 환경이 필요합니다:
|
||||||
|
|
||||||
#### 환경 변수
|
#### 환경 변수
|
||||||
```bash
|
기본 사용자는 별도 API 키가 필요 없습니다. `DATA_GO_KR_API_KEY` 는 hosted/self-host `k-skill-proxy` 운영자가 서버에 설정하는 값입니다.
|
||||||
export DATA_GO_KR_API_KEY="your_api_key_here"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 의존성
|
#### 의존성
|
||||||
- Python 3.7+
|
- Python 3.7+
|
||||||
|
|
|
||||||
|
|
@ -89,10 +89,9 @@ KSKILL_PROXY_BASE_URL=
|
||||||
### startup-support
|
### startup-support
|
||||||
|
|
||||||
#### API 키 관리
|
#### API 키 관리
|
||||||
- **환경 변수**: `DATA_GO_KR_API_KEY`
|
- 기본 사용자는 `DATA_GO_KR_API_KEY` 를 설정하지 않습니다.
|
||||||
- **용도**: 공공데이터포털 API 인증
|
- `DATA_GO_KR_API_KEY` 는 hosted/self-host `k-skill-proxy` 운영자가 서버에 설정하는 upstream 키입니다.
|
||||||
- **보안**: 절대 코드에 하드코딩하지 않음
|
- 로컬에서 직접 공공데이터포털 API를 호출하는 실험 경로에서만 사용자 환경에 임시로 둘 수 있습니다.
|
||||||
- **관리**: 환경 변수 또는 시크릿 매니저를 통해 관리
|
|
||||||
|
|
||||||
#### 데이터 보안
|
#### 데이터 보안
|
||||||
- **데이터 소스**: 공공기관 공식 API만 사용
|
- **데이터 소스**: 공공기관 공식 API만 사용
|
||||||
|
|
|
||||||
|
|
@ -65,12 +65,7 @@ metadata:
|
||||||
|
|
||||||
### Proxy Integration
|
### Proxy Integration
|
||||||
|
|
||||||
API 요청은 `k-skill-proxy`의 `/v1/startup-support/*` 라우트로 중계되며, 다음과 같은 엔드포인트를 사용:
|
공공데이터포털 K-Startup OpenAPI는 `kstartup-search` 스킬과 `k-skill-proxy`의 `/v1/kstartup/*` 라우트가 담당한다. 이 스킬의 helper는 지역별 공개 API 목록을 조회하고, 상세 정보는 결과의 공식 `url` 로 확인한다.
|
||||||
|
|
||||||
- `/v1/startup-support/list` - 지원사업 목록 조회
|
|
||||||
- `/v1/startup-support/detail/<program_id>` - 특정 지원사업 상세 정보
|
|
||||||
- `/v1/startup-support/region/<region>` - 특정 지역 지원사업 조회
|
|
||||||
- `/v1/startup-support/deadline` - 임박 마감 지원사업
|
|
||||||
|
|
||||||
## Output format
|
## Output format
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import os
|
||||||
|
|
||||||
class StartupSupportAPI:
|
class StartupSupportAPI:
|
||||||
"""스타트업 지원사업 API 클라이언트"""
|
"""스타트업 지원사업 API 클라이언트"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.base_urls = {
|
self.base_urls = {
|
||||||
'seoul': 'https://seoulstartup.go.kr',
|
'seoul': 'https://seoulstartup.go.kr',
|
||||||
|
|
@ -19,96 +19,96 @@ class StartupSupportAPI:
|
||||||
'daegu': 'https://daegu-startup.kr',
|
'daegu': 'https://daegu-startup.kr',
|
||||||
'nationwide': 'https://www.data.go.kr'
|
'nationwide': 'https://www.data.go.kr'
|
||||||
}
|
}
|
||||||
|
|
||||||
# 공공데이터포털 API 키 (환경 변수에서 가져오기)
|
# 공공데이터포털 API 키 (환경 변수에서 가져오기)
|
||||||
self.data_go_kr_api_key = os.getenv('DATA_GO_KR_API_KEY')
|
self.data_go_kr_api_key = os.getenv('DATA_GO_KR_API_KEY')
|
||||||
|
|
||||||
# 헤더 설정
|
# 헤더 설정
|
||||||
self.headers = {
|
self.headers = {
|
||||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
|
|
||||||
def search_programs(self, region: str = '전국', keyword: str = None,
|
def search_programs(self, region: str = '전국', keyword: Optional[str] = None,
|
||||||
support_type: str = None, deadline_only: bool = False) -> List[Dict]:
|
support_type: Optional[str] = None, deadline_only: bool = False) -> List[Dict]:
|
||||||
"""
|
"""
|
||||||
지원사업 검색
|
지원사업 검색
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
region: 지역 (서울특별시, 경기도, 부산광역시 등)
|
region: 지역 (서울특별시, 경기도, 부산광역시 등)
|
||||||
keyword: 검색 키워드
|
keyword: 검색 키워드
|
||||||
support_type: 지원 유형 (보조금, 융자, 멘토링 등)
|
support_type: 지원 유형 (보조금, 융자, 멘토링 등)
|
||||||
deadline_only: 마감 임박 사업만 검색
|
deadline_only: 마감 임박 사업만 검색
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
지원사업 목록
|
지원사업 목록
|
||||||
"""
|
"""
|
||||||
programs = []
|
programs = []
|
||||||
|
|
||||||
# 1. 공공데이터포털 API 호출
|
# 1. 공공데이터포털 API 호출
|
||||||
if self.data_go_kr_api_key:
|
if self.data_go_kr_api_key:
|
||||||
data_go_kr_programs = self._search_data_go_kr(region, keyword, support_type)
|
data_go_kr_programs = self._search_data_go_kr(region, keyword, support_type)
|
||||||
programs.extend(data_go_kr_programs)
|
programs.extend(data_go_kr_programs)
|
||||||
|
|
||||||
# 2. 지자체별 API 호출
|
# 2. 지자체별 API 호출
|
||||||
region_programs = self._search_by_region(region, keyword, support_type)
|
region_programs = self._search_by_region(region, keyword, support_type)
|
||||||
programs.extend(region_programs)
|
programs.extend(region_programs)
|
||||||
|
|
||||||
# 3. 마감 임박 필터링
|
# 3. 마감 임박 필터링
|
||||||
if deadline_only:
|
if deadline_only:
|
||||||
programs = self._filter_upcoming_deadline(programs)
|
programs = self._filter_upcoming_deadline(programs)
|
||||||
|
|
||||||
# 중복 제거
|
# 중복 제거
|
||||||
programs = self._remove_duplicates(programs)
|
programs = self._remove_duplicates(programs)
|
||||||
|
|
||||||
# 정렬
|
# 정렬
|
||||||
programs = self._sort_programs(programs)
|
programs = self._sort_programs(programs)
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
|
|
||||||
def _search_data_go_kr(self, region: str, keyword: str, support_type: str) -> List[Dict]:
|
def _search_data_go_kr(self, region: str, keyword: Optional[str], support_type: Optional[str]) -> List[Dict]:
|
||||||
"""공공데이터포털 API로 검색"""
|
"""공공데이터포털 API로 검색"""
|
||||||
programs = []
|
programs = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 중소벤처기업부 스타트업 지원사업 API
|
# 중소벤처기업부 스타트업 지원사업 API
|
||||||
url = "https://www.data.go.kr/api/15058530/openapi"
|
url = "https://www.data.go.kr/api/15058530/openapi"
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
'serviceKey': self.data_go_kr_api_key,
|
'serviceKey': self.data_go_kr_api_key,
|
||||||
'pageNo': '1',
|
'pageNo': '1',
|
||||||
'numOfRows': '100',
|
'numOfRows': '100',
|
||||||
'_type': 'json'
|
'_type': 'json'
|
||||||
}
|
}
|
||||||
|
|
||||||
if region and region != '전국':
|
if region and region != '전국':
|
||||||
params['cnpCdNm'] = region
|
params['cnpCdNm'] = region
|
||||||
|
|
||||||
if keyword:
|
if keyword:
|
||||||
params['panNm'] = keyword
|
params['panNm'] = keyword
|
||||||
|
|
||||||
response = requests.get(url, params=params, headers=self.headers, timeout=10)
|
response = requests.get(url, params=params, headers=self.headers, timeout=10)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
||||||
# 실제 API 응답 구조에 따라 데이터 추출
|
# 실제 API 응답 구조에 따라 데이터 추출
|
||||||
if 'items' in data:
|
if 'items' in data:
|
||||||
for item in data['items']:
|
for item in data['items']:
|
||||||
program = self._parse_program_from_data_go_kr(item)
|
program = self._parse_program_from_data_go_kr(item)
|
||||||
if program:
|
if program:
|
||||||
programs.append(program)
|
programs.append(program)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"공공데이터포털 API 오류: {e}")
|
print(f"공공데이터포털 API 오류: {e}")
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
|
|
||||||
def _search_by_region(self, region: str, keyword: str, support_type: str) -> List[Dict]:
|
def _search_by_region(self, region: str, keyword: Optional[str], support_type: Optional[str]) -> List[Dict]:
|
||||||
"""지자체별 API로 검색"""
|
"""지자체별 API로 검색"""
|
||||||
programs = []
|
programs = []
|
||||||
|
|
||||||
# 지자체별 API 엔드포인트
|
# 지자체별 API 엔드포인트
|
||||||
region_apis = {
|
region_apis = {
|
||||||
'서울특별시': {
|
'서울특별시': {
|
||||||
|
|
@ -132,36 +132,38 @@ class StartupSupportAPI:
|
||||||
'method': 'GET'
|
'method': 'GET'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# 해당 지역 API 호출
|
target_regions = list(region_apis) if region == '전국' else [region]
|
||||||
if region in region_apis:
|
for target_region in target_regions:
|
||||||
api_info = region_apis[region]
|
if target_region not in region_apis:
|
||||||
|
continue
|
||||||
|
api_info = region_apis[target_region]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
params = {}
|
params = {}
|
||||||
if keyword:
|
if keyword:
|
||||||
params['keyword'] = keyword
|
params['keyword'] = keyword
|
||||||
if support_type:
|
if support_type:
|
||||||
params['type'] = support_type
|
params['type'] = support_type
|
||||||
|
|
||||||
response = requests.get(api_info['url'], params=params,
|
response = requests.get(api_info['url'], params=params,
|
||||||
headers=self.headers, timeout=10)
|
headers=self.headers, timeout=10)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
||||||
# 실제 API 응답 구조에 따라 데이터 추출
|
# 실제 API 응답 구조에 따라 데이터 추출
|
||||||
if 'programs' in data:
|
if 'programs' in data:
|
||||||
for item in data['programs']:
|
for item in data['programs']:
|
||||||
program = self._parse_program_from_region_api(item, region)
|
program = self._parse_program_from_region_api(item, target_region)
|
||||||
if program:
|
if program:
|
||||||
programs.append(program)
|
programs.append(program)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"{region} API 오류: {e}")
|
print(f"{target_region} API 오류: {e}")
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
|
|
||||||
def _parse_program_from_data_go_kr(self, item: Dict) -> Optional[Dict]:
|
def _parse_program_from_data_go_kr(self, item: Dict) -> Optional[Dict]:
|
||||||
"""공공데이터포털 응답 파싱"""
|
"""공공데이터포털 응답 파싱"""
|
||||||
try:
|
try:
|
||||||
|
|
@ -179,17 +181,17 @@ class StartupSupportAPI:
|
||||||
'source': '공공데이터포털',
|
'source': '공공데이터포털',
|
||||||
'last_updated': item.get('last_updated', datetime.now().strftime('%Y-%m-%d'))
|
'last_updated': item.get('last_updated', datetime.now().strftime('%Y-%m-%d'))
|
||||||
}
|
}
|
||||||
|
|
||||||
# 필수 필드 검증
|
# 필수 필드 검증
|
||||||
if not program['title']:
|
if not program['title']:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return program
|
return program
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"공공데이터포털 데이터 파싱 오류: {e}")
|
print(f"공공데이터포털 데이터 파싱 오류: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _parse_program_from_region_api(self, item: Dict, region: str) -> Optional[Dict]:
|
def _parse_program_from_region_api(self, item: Dict, region: str) -> Optional[Dict]:
|
||||||
"""지자체 API 응답 파싱"""
|
"""지자체 API 응답 파싱"""
|
||||||
try:
|
try:
|
||||||
|
|
@ -207,24 +209,24 @@ class StartupSupportAPI:
|
||||||
'source': region + ' 창업진흥원',
|
'source': region + ' 창업진흥원',
|
||||||
'last_updated': item.get('last_updated', datetime.now().strftime('%Y-%m-%d'))
|
'last_updated': item.get('last_updated', datetime.now().strftime('%Y-%m-%d'))
|
||||||
}
|
}
|
||||||
|
|
||||||
# 필수 필드 검증
|
# 필수 필드 검증
|
||||||
if not program['title']:
|
if not program['title']:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return program
|
return program
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"지자체 API 데이터 파싱 오류: {e}")
|
print(f"지자체 API 데이터 파싱 오류: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _filter_upcoming_deadline(self, programs: List[Dict]) -> List[Dict]:
|
def _filter_upcoming_deadline(self, programs: List[Dict]) -> List[Dict]:
|
||||||
"""마감 임박 사업 필터링"""
|
"""마감 임박 사업 필터링"""
|
||||||
today = datetime.now()
|
today = datetime.now()
|
||||||
upcoming_threshold = today + timedelta(days=7) # 7일 이내
|
upcoming_threshold = today + timedelta(days=7) # 7일 이내
|
||||||
|
|
||||||
filtered = []
|
filtered = []
|
||||||
|
|
||||||
for program in programs:
|
for program in programs:
|
||||||
if program['deadline']:
|
if program['deadline']:
|
||||||
try:
|
try:
|
||||||
|
|
@ -234,22 +236,22 @@ class StartupSupportAPI:
|
||||||
except:
|
except:
|
||||||
# 날짜 파싱 실패 시 제외
|
# 날짜 파싱 실패 시 제외
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return filtered
|
return filtered
|
||||||
|
|
||||||
def _remove_duplicates(self, programs: List[Dict]) -> List[Dict]:
|
def _remove_duplicates(self, programs: List[Dict]) -> List[Dict]:
|
||||||
"""중복 제거"""
|
"""중복 제거"""
|
||||||
seen_ids = set()
|
seen_ids = set()
|
||||||
unique_programs = []
|
unique_programs = []
|
||||||
|
|
||||||
for program in programs:
|
for program in programs:
|
||||||
program_id = program['id']
|
program_id = program['id']
|
||||||
if program_id not in seen_ids:
|
if program_id not in seen_ids:
|
||||||
seen_ids.add(program_id)
|
seen_ids.add(program_id)
|
||||||
unique_programs.append(program)
|
unique_programs.append(program)
|
||||||
|
|
||||||
return unique_programs
|
return unique_programs
|
||||||
|
|
||||||
def _sort_programs(self, programs: List[Dict]) -> List[Dict]:
|
def _sort_programs(self, programs: List[Dict]) -> List[Dict]:
|
||||||
"""사업 정렬"""
|
"""사업 정렬"""
|
||||||
# 마감일 기준으로 정렬 (가까운 순)
|
# 마감일 기준으로 정렬 (가까운 순)
|
||||||
|
|
@ -260,9 +262,9 @@ class StartupSupportAPI:
|
||||||
except:
|
except:
|
||||||
return datetime.max
|
return datetime.max
|
||||||
return datetime.max
|
return datetime.max
|
||||||
|
|
||||||
return sorted(programs, key=get_deadline)
|
return sorted(programs, key=get_deadline)
|
||||||
|
|
||||||
def get_program_detail(self, program_id: str) -> Optional[Dict]:
|
def get_program_detail(self, program_id: str) -> Optional[Dict]:
|
||||||
"""특정 지원사업 상세 정보 조회"""
|
"""특정 지원사업 상세 정보 조회"""
|
||||||
# ID에 따라 적절한 소스에서 상세 정보 조회
|
# ID에 따라 적절한 소스에서 상세 정보 조회
|
||||||
|
|
@ -272,86 +274,26 @@ class StartupSupportAPI:
|
||||||
return self._get_region_detail(program_id)
|
return self._get_region_detail(program_id)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _get_data_go_kr_detail(self, program_id: str) -> Optional[Dict]:
|
def _get_data_go_kr_detail(self, program_id: str) -> Optional[Dict]:
|
||||||
"""공공데이터포털 상세 정보 조회"""
|
"""공공데이터포털 상세 정보 조회"""
|
||||||
# 실 구현에서는 program_id를 사용해 상세 API 호출
|
return None
|
||||||
return {
|
|
||||||
'id': program_id,
|
|
||||||
'title': '상세 정보 조회 예시',
|
|
||||||
'organization': '중소벤처기업부',
|
|
||||||
'region': '전국',
|
|
||||||
'support_type': '보조금',
|
|
||||||
'amount': '최대 1억원',
|
|
||||||
'deadline': '2024-12-31',
|
|
||||||
'target': '중소기업 창업자',
|
|
||||||
'requirements': [
|
|
||||||
'사업자등록증',
|
|
||||||
'사업계획서',
|
|
||||||
'재무제표',
|
|
||||||
'창업자 신분증'
|
|
||||||
],
|
|
||||||
'application_process': [
|
|
||||||
'온라인 신청서 작성',
|
|
||||||
'서류 제출',
|
|
||||||
'서류 심사',
|
|
||||||
'현장 면접',
|
|
||||||
'결공고'
|
|
||||||
],
|
|
||||||
'contact': {
|
|
||||||
'phone': '02-1234-5678',
|
|
||||||
'email': 'support@smbs.or.kr',
|
|
||||||
'address': '서울시 강남구 테헤란로 123'
|
|
||||||
},
|
|
||||||
'url': 'https://www.data.go.kr/program/detail',
|
|
||||||
'source': '공공데이터포털',
|
|
||||||
'last_updated': datetime.now().strftime('%Y-%m-%d')
|
|
||||||
}
|
|
||||||
|
|
||||||
def _get_region_detail(self, program_id: str) -> Optional[Dict]:
|
def _get_region_detail(self, program_id: str) -> Optional[Dict]:
|
||||||
"""지자체 상세 정보 조회"""
|
"""지자체 상세 정보 조회"""
|
||||||
# 실 구현에서는 program_id를 사용해 상세 API 호출
|
return None
|
||||||
return {
|
|
||||||
'id': program_id,
|
|
||||||
'title': '지자체 상세 정보 조회 예시',
|
|
||||||
'organization': '서울시 창업진흥원',
|
|
||||||
'region': '서울특별시',
|
|
||||||
'support_type': '보조금',
|
|
||||||
'amount': '최대 5천만원',
|
|
||||||
'deadline': '2024-12-31',
|
|
||||||
'target': '서울시 내 스타트업',
|
|
||||||
'requirements': [
|
|
||||||
'사업자등록증',
|
|
||||||
'사업계획서',
|
|
||||||
'재무제표'
|
|
||||||
],
|
|
||||||
'application_process': [
|
|
||||||
'온라인 신청서 작성',
|
|
||||||
'서류 제출',
|
|
||||||
'서류 심사',
|
|
||||||
'결공고'
|
|
||||||
],
|
|
||||||
'contact': {
|
|
||||||
'phone': '02-1234-5678',
|
|
||||||
'email': 'startup@seoul.go.kr',
|
|
||||||
'address': '서울시 강남구 테헤란로 123'
|
|
||||||
},
|
|
||||||
'url': 'https://seoulstartup.go.kr/program/detail',
|
|
||||||
'source': '서울시 창업진흥원',
|
|
||||||
'last_updated': datetime.now().strftime('%Y-%m-%d')
|
|
||||||
}
|
|
||||||
|
|
||||||
def search_startup_support(region: str = '전국', keyword: str = None,
|
def search_startup_support(region: str = '전국', keyword: Optional[str] = None,
|
||||||
support_type: str = None, deadline_only: bool = False) -> List[Dict]:
|
support_type: Optional[str] = None, deadline_only: bool = False) -> List[Dict]:
|
||||||
"""
|
"""
|
||||||
스타트업 지원사업 검색 함수
|
스타트업 지원사업 검색 함수
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
region: 지역 (서울특별시, 경기도, 부산광역시 등)
|
region: 지역 (서울특별시, 경기도, 부산광역시 등)
|
||||||
keyword: 검색 키워드
|
keyword: 검색 키워드
|
||||||
support_type: 지원 유형 (보조금, 융자, 멘토링 등)
|
support_type: 지원 유형 (보조금, 융자, 멘토링 등)
|
||||||
deadline_only: 마감 임박 사업만 검색
|
deadline_only: 마감 임박 사업만 검색
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
지원사업 목록
|
지원사업 목록
|
||||||
"""
|
"""
|
||||||
|
|
@ -361,10 +303,10 @@ def search_startup_support(region: str = '전국', keyword: str = None,
|
||||||
def get_startup_program_detail(program_id: str) -> Optional[Dict]:
|
def get_startup_program_detail(program_id: str) -> Optional[Dict]:
|
||||||
"""
|
"""
|
||||||
특정 지원사업 상세 정보 조회 함수
|
특정 지원사업 상세 정보 조회 함수
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
program_id: 지원사업 ID
|
program_id: 지원사업 ID
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
지원사업 상세 정보
|
지원사업 상세 정보
|
||||||
"""
|
"""
|
||||||
|
|
@ -374,19 +316,19 @@ def get_startup_program_detail(program_id: str) -> Optional[Dict]:
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# 테스트용 실행
|
# 테스트용 실행
|
||||||
print("스타트업 지원사업 검색 테스트")
|
print("스타트업 지원사업 검색 테스트")
|
||||||
|
|
||||||
# 전체 검색
|
# 전체 검색
|
||||||
programs = search_startup_support()
|
programs = search_startup_support()
|
||||||
print(f"총 {len(programs)}개 지원사업 발견")
|
print(f"총 {len(programs)}개 지원사업 발견")
|
||||||
|
|
||||||
# 서울 검색
|
# 서울 검색
|
||||||
seoul_programs = search_startup_support(region='서울특별시')
|
seoul_programs = search_startup_support(region='서울특별시')
|
||||||
print(f"서울 지원사업: {len(seoul_programs)}개")
|
print(f"서울 지원사업: {len(seoul_programs)}개")
|
||||||
|
|
||||||
# 키워드 검색
|
# 키워드 검색
|
||||||
keyword_programs = search_startup_support(keyword='청년')
|
keyword_programs = search_startup_support(keyword='청년')
|
||||||
print(f"'청년' 키워드 검색 결과: {len(keyword_programs)}개")
|
print(f"'청년' 키워드 검색 결과: {len(keyword_programs)}개")
|
||||||
|
|
||||||
# 마감 임박 검색
|
# 마감 임박 검색
|
||||||
deadline_programs = search_startup_support(deadline_only=True)
|
deadline_programs = search_startup_support(deadline_only=True)
|
||||||
print(f"마감 임박 지원사업: {len(deadline_programs)}개")
|
print(f"마감 임박 지원사업: {len(deadline_programs)}개")
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ from datetime import datetime, timedelta
|
||||||
|
|
||||||
# 현재 디렉토리에서 모듈 임포트
|
# 현재 디렉토리에서 모듈 임포트
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
from startup_support import search_startup_support, get_startup_program_detail
|
from startup_support import search_startup_support, get_startup_program_detail, StartupSupportAPI
|
||||||
|
|
||||||
class TestStartupSupport(unittest.TestCase):
|
class TestStartupSupport(unittest.TestCase):
|
||||||
"""스타트업 지원사업 API 테스트"""
|
"""스타트업 지원사업 API 테스트"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""테스트 초기화"""
|
"""테스트 초기화"""
|
||||||
soon_deadline = (datetime.now() + timedelta(days=5)).strftime('%Y-%m-%d')
|
soon_deadline = (datetime.now() + timedelta(days=5)).strftime('%Y-%m-%d')
|
||||||
|
|
@ -47,7 +47,7 @@ class TestStartupSupport(unittest.TestCase):
|
||||||
'last_updated': '2024-05-20'
|
'last_updated': '2024-05-20'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@patch('startup_support.StartupSupportAPI._search_data_go_kr')
|
@patch('startup_support.StartupSupportAPI._search_data_go_kr')
|
||||||
@patch('startup_support.StartupSupportAPI._search_by_region')
|
@patch('startup_support.StartupSupportAPI._search_by_region')
|
||||||
def test_search_programs_basic(self, mock_region_search, mock_data_go_kr_search):
|
def test_search_programs_basic(self, mock_region_search, mock_data_go_kr_search):
|
||||||
|
|
@ -55,15 +55,53 @@ class TestStartupSupport(unittest.TestCase):
|
||||||
# 모킹 설정
|
# 모킹 설정
|
||||||
mock_data_go_kr_search.return_value = []
|
mock_data_go_kr_search.return_value = []
|
||||||
mock_region_search.return_value = self.test_programs
|
mock_region_search.return_value = self.test_programs
|
||||||
|
|
||||||
# 검색 실행
|
# 검색 실행
|
||||||
result = search_startup_support()
|
result = search_startup_support()
|
||||||
|
|
||||||
# 결과 확인
|
# 결과 확인
|
||||||
self.assertEqual(len(result), 2)
|
self.assertEqual(len(result), 2)
|
||||||
self.assertEqual(result[0]['title'], '경기도 MVP 지원사업')
|
self.assertEqual(result[0]['title'], '경기도 MVP 지원사업')
|
||||||
self.assertEqual(result[1]['title'], '서울시 청년 스타트업 창업 지원금')
|
self.assertEqual(result[1]['title'], '서울시 청년 스타트업 창업 지원금')
|
||||||
|
|
||||||
|
@patch('startup_support.requests.get')
|
||||||
|
def test_nationwide_search_aggregates_configured_regions_without_api_key(self, mock_get):
|
||||||
|
payloads = {
|
||||||
|
'https://seoulstartup.go.kr/api/program/list': {
|
||||||
|
'programs': [{
|
||||||
|
'id': 'seoul_1',
|
||||||
|
'title': '서울 창업 지원',
|
||||||
|
'deadline': '2026-06-03',
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
'https://g-startup.kr/api/support/list': {
|
||||||
|
'programs': [{
|
||||||
|
'id': 'gyeonggi_1',
|
||||||
|
'title': '경기 창업 지원',
|
||||||
|
'deadline': '2026-06-04',
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def fake_get(url, **_):
|
||||||
|
response = MagicMock()
|
||||||
|
response.status_code = 200
|
||||||
|
response.json.return_value = payloads.get(url, {'programs': []})
|
||||||
|
return response
|
||||||
|
|
||||||
|
mock_get.side_effect = fake_get
|
||||||
|
|
||||||
|
with patch.dict(os.environ, {}, clear=True):
|
||||||
|
result = search_startup_support(region='전국')
|
||||||
|
|
||||||
|
titles = {program['title'] for program in result}
|
||||||
|
self.assertIn('서울 창업 지원', titles)
|
||||||
|
self.assertIn('경기 창업 지원', titles)
|
||||||
|
|
||||||
|
def test_builtin_detail_lookup_does_not_return_fabricated_sample_data(self):
|
||||||
|
self.assertIsNone(get_startup_program_detail('data_gov_missing'))
|
||||||
|
self.assertIsNone(get_startup_program_detail('서울_missing'))
|
||||||
|
|
||||||
@patch('startup_support.StartupSupportAPI._search_data_go_kr')
|
@patch('startup_support.StartupSupportAPI._search_data_go_kr')
|
||||||
@patch('startup_support.StartupSupportAPI._search_by_region')
|
@patch('startup_support.StartupSupportAPI._search_by_region')
|
||||||
def test_search_programs_seoul_only(self, mock_region_search, mock_data_go_kr_search):
|
def test_search_programs_seoul_only(self, mock_region_search, mock_data_go_kr_search):
|
||||||
|
|
@ -71,14 +109,14 @@ class TestStartupSupport(unittest.TestCase):
|
||||||
# 모킹 설정
|
# 모킹 설정
|
||||||
mock_data_go_kr_search.return_value = []
|
mock_data_go_kr_search.return_value = []
|
||||||
mock_region_search.return_value = [self.test_programs[0]] # 서울 프로그램만
|
mock_region_search.return_value = [self.test_programs[0]] # 서울 프로그램만
|
||||||
|
|
||||||
# 검색 실행
|
# 검색 실행
|
||||||
result = search_startup_support(region='서울특별시')
|
result = search_startup_support(region='서울특별시')
|
||||||
|
|
||||||
# 결과 확인
|
# 결과 확인
|
||||||
self.assertEqual(len(result), 1)
|
self.assertEqual(len(result), 1)
|
||||||
self.assertEqual(result[0]['region'], '서울특별시')
|
self.assertEqual(result[0]['region'], '서울특별시')
|
||||||
|
|
||||||
@patch('startup_support.StartupSupportAPI._search_data_go_kr')
|
@patch('startup_support.StartupSupportAPI._search_data_go_kr')
|
||||||
@patch('startup_support.StartupSupportAPI._search_by_region')
|
@patch('startup_support.StartupSupportAPI._search_by_region')
|
||||||
def test_search_programs_keyword_search(self, mock_region_search, mock_data_go_kr_search):
|
def test_search_programs_keyword_search(self, mock_region_search, mock_data_go_kr_search):
|
||||||
|
|
@ -86,14 +124,14 @@ class TestStartupSupport(unittest.TestCase):
|
||||||
# 모킹 설정
|
# 모킹 설정
|
||||||
mock_data_go_kr_search.return_value = []
|
mock_data_go_kr_search.return_value = []
|
||||||
mock_region_search.return_value = [self.test_programs[1]] # MVP 프로그램만
|
mock_region_search.return_value = [self.test_programs[1]] # MVP 프로그램만
|
||||||
|
|
||||||
# 검색 실행
|
# 검색 실행
|
||||||
result = search_startup_support(keyword='MVP')
|
result = search_startup_support(keyword='MVP')
|
||||||
|
|
||||||
# 결과 확인
|
# 결과 확인
|
||||||
self.assertEqual(len(result), 1)
|
self.assertEqual(len(result), 1)
|
||||||
self.assertEqual(result[0]['title'], '경기도 MVP 지원사업')
|
self.assertEqual(result[0]['title'], '경기도 MVP 지원사업')
|
||||||
|
|
||||||
@patch('startup_support.StartupSupportAPI._search_data_go_kr')
|
@patch('startup_support.StartupSupportAPI._search_data_go_kr')
|
||||||
@patch('startup_support.StartupSupportAPI._search_by_region')
|
@patch('startup_support.StartupSupportAPI._search_by_region')
|
||||||
def test_search_programs_deadline_only(self, mock_region_search, mock_data_go_kr_search):
|
def test_search_programs_deadline_only(self, mock_region_search, mock_data_go_kr_search):
|
||||||
|
|
@ -101,7 +139,7 @@ class TestStartupSupport(unittest.TestCase):
|
||||||
# 모킹 설정
|
# 모킹 설정
|
||||||
mock_data_go_kr_search.return_value = []
|
mock_data_go_kr_search.return_value = []
|
||||||
mock_region_search.return_value = self.test_programs
|
mock_region_search.return_value = self.test_programs
|
||||||
|
|
||||||
# 검색 실행
|
# 검색 실행
|
||||||
result = search_startup_support(deadline_only=True)
|
result = search_startup_support(deadline_only=True)
|
||||||
|
|
||||||
|
|
@ -110,39 +148,37 @@ class TestStartupSupport(unittest.TestCase):
|
||||||
for program in result:
|
for program in result:
|
||||||
deadline = datetime.strptime(program['deadline'], '%Y-%m-%d')
|
deadline = datetime.strptime(program['deadline'], '%Y-%m-%d')
|
||||||
self.assertTrue(datetime.now() <= deadline <= datetime.now() + timedelta(days=7))
|
self.assertTrue(datetime.now() <= deadline <= datetime.now() + timedelta(days=7))
|
||||||
|
|
||||||
@patch('startup_support.StartupSupportAPI._get_data_go_kr_detail')
|
@patch('startup_support.StartupSupportAPI._get_data_go_kr_detail')
|
||||||
def test_get_program_detail_data_gov(self, mock_get_detail):
|
def test_get_program_detail_data_gov(self, mock_get_detail):
|
||||||
"""공공데이터포털 상세 정보 조회 테스트"""
|
"""공공데이터포털 상세 정보 조회 테스트"""
|
||||||
# 모킹 설정
|
# 모킹 설정
|
||||||
mock_get_detail.return_value = self.test_programs[0]
|
mock_get_detail.return_value = self.test_programs[0]
|
||||||
|
|
||||||
# 상세 정보 조회
|
# 상세 정보 조회
|
||||||
result = get_startup_program_detail('data_gov_test_001')
|
result = get_startup_program_detail('data_gov_test_001')
|
||||||
|
|
||||||
# 결과 확인
|
# 결과 확인
|
||||||
self.assertIsNotNone(result)
|
self.assertIsNotNone(result)
|
||||||
self.assertEqual(result['title'], '서울시 청년 스타트업 창업 지원금')
|
self.assertEqual(result['title'], '서울시 청년 스타트업 창업 지원금')
|
||||||
|
|
||||||
@patch('startup_support.StartupSupportAPI._get_region_detail')
|
@patch('startup_support.StartupSupportAPI._get_region_detail')
|
||||||
def test_get_program_detail_region(self, mock_get_detail):
|
def test_get_program_detail_region(self, mock_get_detail):
|
||||||
"""지자체 상세 정보 조회 테스트"""
|
"""지자체 상세 정보 조회 테스트"""
|
||||||
# 모킹 설정
|
# 모킹 설정
|
||||||
mock_get_detail.return_value = self.test_programs[1]
|
mock_get_detail.return_value = self.test_programs[1]
|
||||||
|
|
||||||
# 상세 정보 조회
|
# 상세 정보 조회
|
||||||
result = get_startup_program_detail('서울_test_001')
|
result = get_startup_program_detail('서울_test_001')
|
||||||
|
|
||||||
# 결과 확인
|
# 결과 확인
|
||||||
self.assertIsNotNone(result)
|
self.assertIsNotNone(result)
|
||||||
self.assertEqual(result['title'], '경기도 MVP 지원사업')
|
self.assertEqual(result['title'], '경기도 MVP 지원사업')
|
||||||
|
|
||||||
def test_parse_program_from_data_go_kr(self):
|
def test_parse_program_from_data_go_kr(self):
|
||||||
"""공공데이터포털 데이터 파싱 테스트"""
|
"""공공데이터포털 데이터 파싱 테스트"""
|
||||||
from startup_support import StartupSupportAPI
|
|
||||||
|
|
||||||
api = StartupSupportAPI()
|
api = StartupSupportAPI()
|
||||||
|
|
||||||
# 테스트 데이터
|
# 테스트 데이터
|
||||||
item = {
|
item = {
|
||||||
'pan_id': 'test_001',
|
'pan_id': 'test_001',
|
||||||
|
|
@ -156,22 +192,22 @@ class TestStartupSupport(unittest.TestCase):
|
||||||
'detail_url': 'https://test.com',
|
'detail_url': 'https://test.com',
|
||||||
'last_updated': '2024-05-20'
|
'last_updated': '2024-05-20'
|
||||||
}
|
}
|
||||||
|
|
||||||
# 파싱 실행
|
# 파싱 실행
|
||||||
result = api._parse_program_from_data_go_kr(item)
|
result = api._parse_program_from_data_go_kr(item)
|
||||||
|
|
||||||
# 결과 확인
|
# 결과 확인
|
||||||
self.assertIsNotNone(result)
|
self.assertIsNotNone(result)
|
||||||
self.assertEqual(result['title'], '테스트 지원사업')
|
self.assertEqual(result['title'], '테스트 지원사업')
|
||||||
self.assertEqual(result['region'], '서울특별시')
|
self.assertEqual(result['region'], '서울특별시')
|
||||||
self.assertEqual(result['support_type'], '보조금')
|
self.assertEqual(result['support_type'], '보조금')
|
||||||
|
|
||||||
def test_parse_program_from_region_api(self):
|
def test_parse_program_from_region_api(self):
|
||||||
"""지자체 API 데이터 파싱 테스트"""
|
"""지자체 API 데이터 파싱 테스트"""
|
||||||
from startup_support import StartupSupportAPI
|
from startup_support import StartupSupportAPI
|
||||||
|
|
||||||
api = StartupSupportAPI()
|
api = StartupSupportAPI()
|
||||||
|
|
||||||
# 테스트 데이터
|
# 테스트 데이터
|
||||||
item = {
|
item = {
|
||||||
'id': 'test_001',
|
'id': 'test_001',
|
||||||
|
|
@ -184,23 +220,23 @@ class TestStartupSupport(unittest.TestCase):
|
||||||
'url': 'https://test.com',
|
'url': 'https://test.com',
|
||||||
'last_updated': '2024-05-20'
|
'last_updated': '2024-05-20'
|
||||||
}
|
}
|
||||||
|
|
||||||
# 파싱 실행
|
# 파싱 실행
|
||||||
result = api._parse_program_from_region_api(item, '경기도')
|
result = api._parse_program_from_region_api(item, '경기도')
|
||||||
|
|
||||||
# 결과 확인
|
# 결과 확인
|
||||||
self.assertIsNotNone(result)
|
self.assertIsNotNone(result)
|
||||||
self.assertEqual(result['title'], '테스트 지원사업')
|
self.assertEqual(result['title'], '테스트 지원사업')
|
||||||
self.assertEqual(result['organization'], '경기도 창업진흥원')
|
self.assertEqual(result['organization'], '경기도 창업진흥원')
|
||||||
self.assertEqual(result['support_type'], '융자')
|
self.assertEqual(result['support_type'], '융자')
|
||||||
|
|
||||||
def test_filter_upcoming_deadline(self):
|
def test_filter_upcoming_deadline(self):
|
||||||
"""마감 임박 필터링 테스트"""
|
"""마감 임박 필터링 테스트"""
|
||||||
from startup_support import StartupSupportAPI
|
from startup_support import StartupSupportAPI
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
api = StartupSupportAPI()
|
api = StartupSupportAPI()
|
||||||
|
|
||||||
# 테스트 데이터 (다양한 마감일)
|
# 테스트 데이터 (다양한 마감일)
|
||||||
programs = [
|
programs = [
|
||||||
{'deadline': (datetime.now() + timedelta(days=3)).strftime('%Y-%m-%d')}, # 3일 후
|
{'deadline': (datetime.now() + timedelta(days=3)).strftime('%Y-%m-%d')}, # 3일 후
|
||||||
|
|
@ -209,19 +245,19 @@ class TestStartupSupport(unittest.TestCase):
|
||||||
{'deadline': '2024-12-31'}, # 먼 미래
|
{'deadline': '2024-12-31'}, # 먼 미래
|
||||||
{'deadline': ''} # 마감일 없음
|
{'deadline': ''} # 마감일 없음
|
||||||
]
|
]
|
||||||
|
|
||||||
# 필터링 실행
|
# 필터링 실행
|
||||||
result = api._filter_upcoming_deadline(programs)
|
result = api._filter_upcoming_deadline(programs)
|
||||||
|
|
||||||
# 결과 확인 (7일 이내이면서 이미 지난 날짜 제외)
|
# 결과 확인 (7일 이내이면서 이미 지난 날짜 제외)
|
||||||
self.assertEqual(len(result), 1)
|
self.assertEqual(len(result), 1)
|
||||||
|
|
||||||
def test_remove_duplicates(self):
|
def test_remove_duplicates(self):
|
||||||
"""중복 제거 테스트"""
|
"""중복 제거 테스트"""
|
||||||
from startup_support import StartupSupportAPI
|
from startup_support import StartupSupportAPI
|
||||||
|
|
||||||
api = StartupSupportAPI()
|
api = StartupSupportAPI()
|
||||||
|
|
||||||
# 테스트 데이터 (중복 포함)
|
# 테스트 데이터 (중복 포함)
|
||||||
programs = [
|
programs = [
|
||||||
{'id': 'test_001', 'title': '프로그램 A'},
|
{'id': 'test_001', 'title': '프로그램 A'},
|
||||||
|
|
@ -229,10 +265,10 @@ class TestStartupSupport(unittest.TestCase):
|
||||||
{'id': 'test_001', 'title': '프로그램 A (중복)'},
|
{'id': 'test_001', 'title': '프로그램 A (중복)'},
|
||||||
{'id': 'test_003', 'title': '프로그램 C'}
|
{'id': 'test_003', 'title': '프로그램 C'}
|
||||||
]
|
]
|
||||||
|
|
||||||
# 중복 제거 실행
|
# 중복 제거 실행
|
||||||
result = api._remove_duplicates(programs)
|
result = api._remove_duplicates(programs)
|
||||||
|
|
||||||
# 결과 확인 (중복 제외)
|
# 결과 확인 (중복 제외)
|
||||||
self.assertEqual(len(result), 3)
|
self.assertEqual(len(result), 3)
|
||||||
self.assertEqual(result[0]['id'], 'test_001')
|
self.assertEqual(result[0]['id'], 'test_001')
|
||||||
|
|
@ -243,21 +279,21 @@ def run_tests():
|
||||||
"""테스트 실행"""
|
"""테스트 실행"""
|
||||||
# 테스트 스위트 생성
|
# 테스트 스위트 생성
|
||||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestStartupSupport)
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestStartupSupport)
|
||||||
|
|
||||||
# 테스트 실행기 생성
|
# 테스트 실행기 생성
|
||||||
runner = unittest.TextTestRunner(verbosity=2)
|
runner = unittest.TextTestRunner(verbosity=2)
|
||||||
|
|
||||||
# 테스트 실행
|
# 테스트 실행
|
||||||
result = runner.run(suite)
|
result = runner.run(suite)
|
||||||
|
|
||||||
return result.wasSuccessful()
|
return result.wasSuccessful()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print("스타트업 지원사업 API 테스트 시작")
|
print("스타트업 지원사업 API 테스트 시작")
|
||||||
|
|
||||||
# 테스트 실행
|
# 테스트 실행
|
||||||
success = run_tests()
|
success = run_tests()
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
print("✅ 모든 테스트 통과!")
|
print("✅ 모든 테스트 통과!")
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue