.
This commit is contained in:
+525
-2
@@ -145,22 +145,99 @@ GET /api/items/stats
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Получение списка источников
|
### 4. Получение списка источников (sourceName)
|
||||||
|
|
||||||
```http
|
```http
|
||||||
GET /api/items/sources
|
GET /api/items/sources
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Описание:**
|
||||||
|
Возвращает список всех уникальных источников новостей, которые есть в базе данных. Этот эндпоинт полезен для:
|
||||||
|
|
||||||
|
- Построения фильтров по источникам
|
||||||
|
- Отображения списка доступных источников в UI
|
||||||
|
- Динамического создания выпадающих списков
|
||||||
|
|
||||||
**Ответ:**
|
**Ответ:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"success": true,
|
"success": true,
|
||||||
"message": "Источники получены успешно",
|
"message": "Источники получены успешно",
|
||||||
"data": ["Kursiv Media", "Kapital.kz", "LSM.kz", "РБК", "Ведомости"]
|
"data": [
|
||||||
|
"Kursiv (Бизнес/экономика)",
|
||||||
|
"Kapital.kz (Бизнес)",
|
||||||
|
"LSM.kz",
|
||||||
|
"РБК",
|
||||||
|
"Ведомости"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Примеры использования:**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Получить список всех источников
|
||||||
|
const response = await fetch('/api/items/sources');
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
const sources = result.data;
|
||||||
|
console.log('Доступные источники:', sources);
|
||||||
|
// sources = ["Kursiv (Бизнес/экономика)", "Kapital.kz (Бизнес)", "LSM.kz", "РБК", "Ведомости"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Использование в React для создания фильтра
|
||||||
|
function SourceFilter({ onSourceChange }) {
|
||||||
|
const [sources, setSources] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/api/items/sources')
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data.success) {
|
||||||
|
setSources(data.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<select onChange={(e) => onSourceChange(e.target.value)}>
|
||||||
|
<option value=''>Все источники</option>
|
||||||
|
{sources.map((source) => (
|
||||||
|
<option key={source} value={source}>
|
||||||
|
{source}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Использование в Vue.js
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
sources: [],
|
||||||
|
selectedSource: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
const response = await fetch('/api/items/sources');
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
this.sources = result.data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Важные замечания:**
|
||||||
|
|
||||||
|
- Список источников формируется на основе реальных данных в базе
|
||||||
|
- Если в базе нет данных от какого-то источника, он не будет включен в список
|
||||||
|
- Названия источников точно соответствуют тем, что используются в поле `sourceName` при фильтрации
|
||||||
|
- Порядок источников может изменяться в зависимости от того, как MongoDB возвращает уникальные значения
|
||||||
|
|
||||||
### 5. Получение новостей по конкретному источнику
|
### 5. Получение новостей по конкретному источнику
|
||||||
|
|
||||||
```http
|
```http
|
||||||
@@ -485,6 +562,120 @@ GET /api/health/parsers
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 6. Проверка доступности RSS-лент
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/health/rss
|
||||||
|
```
|
||||||
|
|
||||||
|
**Описание:**
|
||||||
|
Проверяет доступность всех RSS-лент и предоставляет рекомендации по альтернативным URL в случае недоступности.
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "Проверка RSS-лент завершена",
|
||||||
|
"data": {
|
||||||
|
"overallStatus": "DEGRADED",
|
||||||
|
"accessibilityResults": {
|
||||||
|
"kursiv": true,
|
||||||
|
"kapital": false,
|
||||||
|
"lsm": true,
|
||||||
|
"rbc": false,
|
||||||
|
"vedomosti": true
|
||||||
|
},
|
||||||
|
"totalFeeds": 5,
|
||||||
|
"accessibleFeeds": 3,
|
||||||
|
"unaccessibleFeeds": 2,
|
||||||
|
"recommendations": {
|
||||||
|
"kapital": "https://kapital.kz/feed/",
|
||||||
|
"rbc": "https://rbc.ru/rss/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Примеры использования:**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Проверка доступности RSS-лент
|
||||||
|
async function checkRssFeeds() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/health/rss');
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
const data = result.data;
|
||||||
|
console.log('Общий статус:', data.overallStatus);
|
||||||
|
console.log('Доступные ленты:', data.accessibleFeeds);
|
||||||
|
console.log('Недоступные ленты:', data.unaccessibleFeeds);
|
||||||
|
|
||||||
|
// Показать рекомендации для недоступных лент
|
||||||
|
if (data.recommendations && Object.keys(data.recommendations).length > 0) {
|
||||||
|
console.log('Рекомендации по альтернативным URL:');
|
||||||
|
Object.entries(data.recommendations).forEach(([source, url]) => {
|
||||||
|
console.log(`${source}: ${url}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка проверки RSS-лент:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// React компонент для отображения статуса RSS-лент
|
||||||
|
function RssStatusWidget() {
|
||||||
|
const [rssStatus, setRssStatus] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/api/health/rss')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
setRssStatus(data.data);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Ошибка загрузки статуса RSS:', err);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (loading) return <div>Загрузка...</div>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rss-status">
|
||||||
|
<h3>Статус RSS-лент</h3>
|
||||||
|
<div className={`status ${rssStatus.overallStatus.toLowerCase()}`}>
|
||||||
|
{rssStatus.overallStatus === 'UP' ? '✅ Все ленты доступны' :
|
||||||
|
rssStatus.overallStatus === 'DEGRADED' ? '⚠️ Некоторые ленты недоступны' :
|
||||||
|
'❌ Ленты недоступны'}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="feeds-list">
|
||||||
|
{Object.entries(rssStatus.accessibilityResults).map(([source, isAccessible]) => (
|
||||||
|
<div key={source} className={`feed-item ${isAccessible ? 'accessible' : 'inaccessible'}`}>
|
||||||
|
<span className="source">{source}</span>
|
||||||
|
<span className="status">
|
||||||
|
{isAccessible ? '✅' : '❌'}
|
||||||
|
</span>
|
||||||
|
{!isAccessible && rssStatus.recommendations[source] && (
|
||||||
|
<div className="recommendation">
|
||||||
|
Рекомендация: {rssStatus.recommendations[source]}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 Примеры использования для фронтенда
|
## 🚀 Примеры использования для фронтенда
|
||||||
@@ -525,6 +716,23 @@ class NewsApiService {
|
|||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Получить источники с обработкой ошибок
|
||||||
|
async getSourcesWithErrorHandling() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${this.baseUrl}/api/items/sources`);
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.message || 'Ошибка при получении источников');
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.data; // Возвращаем только массив источников
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при получении источников:', error);
|
||||||
|
return []; // Возвращаем пустой массив в случае ошибки
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Запустить парсер (админ функция)
|
// Запустить парсер (админ функция)
|
||||||
async runParser(sourceName) {
|
async runParser(sourceName) {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
@@ -577,6 +785,19 @@ const recentNews = await apiService.getNews({
|
|||||||
startDate: weekAgo,
|
startDate: weekAgo,
|
||||||
sort: 'publishedAt,desc',
|
sort: 'publishedAt,desc',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Получить список источников для фильтра
|
||||||
|
const sources = await apiService.getSourcesWithErrorHandling();
|
||||||
|
console.log('Доступные источники:', sources);
|
||||||
|
|
||||||
|
// Получить новости от конкретного источника
|
||||||
|
if (sources.length > 0) {
|
||||||
|
const newsFromFirstSource = await apiService.getNews({
|
||||||
|
sourceName: sources[0],
|
||||||
|
page: 0,
|
||||||
|
size: 10,
|
||||||
|
});
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Vue.js примеры
|
### Vue.js примеры
|
||||||
@@ -608,16 +829,318 @@ export function useNewsApi() {
|
|||||||
return response.json();
|
return response.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getSourcesList = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${baseUrl}/api/items/sources`);
|
||||||
|
const result = await response.json();
|
||||||
|
return result.success ? result.data : [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при получении источников:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getNews,
|
getNews,
|
||||||
getStats,
|
getStats,
|
||||||
getSources,
|
getSources,
|
||||||
|
getSourcesList,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🎯 Практические примеры использования эндпоинта источников
|
||||||
|
|
||||||
|
### 1. Создание динамического фильтра источников
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// React компонент для фильтрации по источникам
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
function NewsFilter({ onFilterChange }) {
|
||||||
|
const [sources, setSources] = useState([]);
|
||||||
|
const [selectedSource, setSelectedSource] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Загружаем список источников при монтировании компонента
|
||||||
|
fetch('/api/items/sources')
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data.success) {
|
||||||
|
setSources(data.data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => console.error('Ошибка загрузки источников:', err));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSourceChange = (source) => {
|
||||||
|
setSelectedSource(source);
|
||||||
|
onFilterChange({ sourceName: source || undefined });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='news-filter'>
|
||||||
|
<label htmlFor='source-filter'>Фильтр по источнику:</label>
|
||||||
|
<select
|
||||||
|
id='source-filter'
|
||||||
|
value={selectedSource}
|
||||||
|
onChange={(e) => handleSourceChange(e.target.value)}
|
||||||
|
>
|
||||||
|
<option value=''>Все источники</option>
|
||||||
|
{sources.map((source) => (
|
||||||
|
<option key={source} value={source}>
|
||||||
|
{source}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewsFilter;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Отображение статистики по источникам
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Vue.js компонент для отображения статистики
|
||||||
|
<template>
|
||||||
|
<div class="sources-stats">
|
||||||
|
<h3>Статистика по источникам</h3>
|
||||||
|
<div v-if="loading" class="loading">Загрузка...</div>
|
||||||
|
<div v-else class="stats-grid">
|
||||||
|
<div
|
||||||
|
v-for="source in sources"
|
||||||
|
:key="source"
|
||||||
|
class="stat-item"
|
||||||
|
@click="filterBySource(source)"
|
||||||
|
>
|
||||||
|
<span class="source-name">{{ source }}</span>
|
||||||
|
<span class="source-count">{{ getSourceCount(source) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
sources: [],
|
||||||
|
loading: true,
|
||||||
|
stats: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.loadSources();
|
||||||
|
await this.loadStats();
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadSources() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/items/sources');
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
this.sources = result.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка загрузки источников:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async loadStats() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/items/stats');
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
this.stats = result.data.sourceStatistics;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка загрузки статистики:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getSourceCount(source) {
|
||||||
|
return this.stats[source] || 0;
|
||||||
|
},
|
||||||
|
filterBySource(source) {
|
||||||
|
this.$emit('filter-change', { sourceName: source });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Автодополнение для поиска по источникам
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Angular сервис для автодополнения
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Observable, BehaviorSubject } from 'rxjs';
|
||||||
|
import { map, catchError } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class SourceService {
|
||||||
|
private sourcesSubject = new BehaviorSubject<string[]>([]);
|
||||||
|
public sources$ = this.sourcesSubject.asObservable();
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) {
|
||||||
|
this.loadSources();
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadSources(): void {
|
||||||
|
this.http.get<any>('/api/items/sources')
|
||||||
|
.pipe(
|
||||||
|
map(response => response.success ? response.data : []),
|
||||||
|
catchError(error => {
|
||||||
|
console.error('Ошибка загрузки источников:', error);
|
||||||
|
return [];
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe(sources => {
|
||||||
|
this.sourcesSubject.next(sources);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
searchSources(query: string): Observable<string[]> {
|
||||||
|
return this.sources$.pipe(
|
||||||
|
map(sources =>
|
||||||
|
sources.filter(source =>
|
||||||
|
source.toLowerCase().includes(query.toLowerCase())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSources(): Observable<string[]> {
|
||||||
|
return this.sources$;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Компонент автодополнения
|
||||||
|
@Component({
|
||||||
|
selector: 'app-source-autocomplete',
|
||||||
|
template: `
|
||||||
|
<div class="autocomplete">
|
||||||
|
<input
|
||||||
|
[(ngModel)]="searchQuery"
|
||||||
|
(input)="onSearch($event.target.value)"
|
||||||
|
placeholder="Поиск источника..."
|
||||||
|
class="search-input"
|
||||||
|
/>
|
||||||
|
<div *ngIf="filteredSources.length > 0" class="suggestions">
|
||||||
|
<div
|
||||||
|
*ngFor="let source of filteredSources"
|
||||||
|
(click)="selectSource(source)"
|
||||||
|
class="suggestion-item"
|
||||||
|
>
|
||||||
|
{{ source }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class SourceAutocompleteComponent {
|
||||||
|
searchQuery = '';
|
||||||
|
filteredSources: string[] = [];
|
||||||
|
|
||||||
|
constructor(private sourceService: SourceService) {}
|
||||||
|
|
||||||
|
onSearch(query: string): void {
|
||||||
|
if (query.length > 0) {
|
||||||
|
this.sourceService.searchSources(query)
|
||||||
|
.subscribe(sources => {
|
||||||
|
this.filteredSources = sources;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.filteredSources = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectSource(source: string): void {
|
||||||
|
this.searchQuery = source;
|
||||||
|
this.filteredSources = [];
|
||||||
|
// Эмитим событие выбора источника
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Кэширование списка источников
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Сервис с кэшированием для React
|
||||||
|
class SourceCacheService {
|
||||||
|
constructor() {
|
||||||
|
this.cache = null;
|
||||||
|
this.cacheTime = null;
|
||||||
|
this.cacheTimeout = 5 * 60 * 1000; // 5 минут
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSources() {
|
||||||
|
// Проверяем, есть ли актуальный кэш
|
||||||
|
if (
|
||||||
|
this.cache &&
|
||||||
|
this.cacheTime &&
|
||||||
|
Date.now() - this.cacheTime < this.cacheTimeout
|
||||||
|
) {
|
||||||
|
return this.cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/items/sources');
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
this.cache = result.data;
|
||||||
|
this.cacheTime = Date.now();
|
||||||
|
return result.data;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка загрузки источников:', error);
|
||||||
|
// Возвращаем кэш, если есть, даже если он устарел
|
||||||
|
return this.cache || [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearCache() {
|
||||||
|
this.cache = null;
|
||||||
|
this.cacheTime = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Использование в React хуке
|
||||||
|
function useSources() {
|
||||||
|
const [sources, setSources] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const sourceService = new SourceCacheService();
|
||||||
|
|
||||||
|
sourceService
|
||||||
|
.getSources()
|
||||||
|
.then((data) => {
|
||||||
|
setSources(data);
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setError(err.message);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { sources, loading, error };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 📝 Важные замечания
|
## 📝 Важные замечания
|
||||||
|
|
||||||
1. **Пагинация**: Все эндпоинты для получения данных поддерживают пагинацию через параметры `page` и `size`.
|
1. **Пагинация**: Все эндпоинты для получения данных поддерживают пагинацию через параметры `page` и `size`.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import kz.konturai.parser.dto.ApiResponse;
|
|||||||
import kz.konturai.parser.dto.HealthCheckDto;
|
import kz.konturai.parser.dto.HealthCheckDto;
|
||||||
import kz.konturai.parser.service.MarketItemService;
|
import kz.konturai.parser.service.MarketItemService;
|
||||||
import kz.konturai.parser.service.ParserManagerService;
|
import kz.konturai.parser.service.ParserManagerService;
|
||||||
|
import kz.konturai.parser.util.RssUrlValidator;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
@@ -26,6 +27,9 @@ public class HealthCheckController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ParserManagerService parserManagerService;
|
private ParserManagerService parserManagerService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RssUrlValidator rssUrlValidator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Проверка состояния парсера
|
* Проверка состояния парсера
|
||||||
*/
|
*/
|
||||||
@@ -150,4 +154,50 @@ public class HealthCheckController {
|
|||||||
.body(ApiResponse.error("Ошибка при проверке парсеров: " + e.getMessage()));
|
.body(ApiResponse.error("Ошибка при проверке парсеров: " + e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка доступности RSS-лент
|
||||||
|
*/
|
||||||
|
@GetMapping("/rss")
|
||||||
|
public ResponseEntity<ApiResponse<java.util.Map<String, Object>>> checkRssFeeds() {
|
||||||
|
try {
|
||||||
|
// Получаем текущие URL из конфигурации
|
||||||
|
java.util.Map<String, String> rssUrls = new java.util.HashMap<>();
|
||||||
|
rssUrls.put("kursiv", "https://kursiv.media/feed/");
|
||||||
|
rssUrls.put("kapital", "https://kapital.kz/rss/");
|
||||||
|
rssUrls.put("lsm", "https://lsm.kz/rss");
|
||||||
|
rssUrls.put("rbc", "https://static.feed.rbc.ru/rbc/logical/footer/news.rss");
|
||||||
|
rssUrls.put("vedomosti", "https://www.vedomosti.ru/rss/rubric/technology/internet");
|
||||||
|
|
||||||
|
// Проверяем доступность
|
||||||
|
java.util.Map<String, Boolean> accessibilityResults = rssUrlValidator.checkAllRssUrls(rssUrls);
|
||||||
|
|
||||||
|
// Формируем детальный отчет
|
||||||
|
java.util.Map<String, Object> report = new java.util.HashMap<>();
|
||||||
|
report.put("overallStatus",
|
||||||
|
accessibilityResults.values().stream().allMatch(Boolean::booleanValue) ? "UP" : "DEGRADED");
|
||||||
|
report.put("accessibilityResults", accessibilityResults);
|
||||||
|
report.put("totalFeeds", rssUrls.size());
|
||||||
|
report.put("accessibleFeeds", accessibilityResults.values().stream().mapToInt(b -> b ? 1 : 0).sum());
|
||||||
|
report.put("unaccessibleFeeds", accessibilityResults.values().stream().mapToInt(b -> b ? 0 : 1).sum());
|
||||||
|
|
||||||
|
// Добавляем рекомендации по альтернативным URL
|
||||||
|
java.util.Map<String, String> recommendations = new java.util.HashMap<>();
|
||||||
|
for (String source : rssUrls.keySet()) {
|
||||||
|
if (!accessibilityResults.getOrDefault(source, false)) {
|
||||||
|
String alternativeUrl = rssUrlValidator.findWorkingUrl(source);
|
||||||
|
if (alternativeUrl != null) {
|
||||||
|
recommendations.put(source, alternativeUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
report.put("recommendations", recommendations);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(ApiResponse.success("Проверка RSS-лент завершена", report));
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ResponseEntity.internalServerError()
|
||||||
|
.body(ApiResponse.error("Ошибка при проверке RSS-лент: " + e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,10 +102,31 @@ public class KapitalParserService implements ParserService {
|
|||||||
* Получение RSS-ленты
|
* Получение RSS-ленты
|
||||||
*/
|
*/
|
||||||
private SyndFeed getRssFeed() throws Exception {
|
private SyndFeed getRssFeed() throws Exception {
|
||||||
URL feedUrl = URI.create(rssFeedUrl).toURL();
|
try {
|
||||||
SyndFeedInput input = new SyndFeedInput();
|
URL feedUrl = URI.create(rssFeedUrl).toURL();
|
||||||
try (XmlReader reader = new XmlReader(feedUrl.openStream())) {
|
SyndFeedInput input = new SyndFeedInput();
|
||||||
return input.build(reader);
|
|
||||||
|
// Добавляем таймаут и обработку ошибок
|
||||||
|
java.net.URLConnection connection = feedUrl.openConnection();
|
||||||
|
connection.setConnectTimeout(10000); // 10 секунд
|
||||||
|
connection.setReadTimeout(30000); // 30 секунд
|
||||||
|
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
||||||
|
|
||||||
|
try (XmlReader reader = new XmlReader(connection.getInputStream())) {
|
||||||
|
return input.build(reader);
|
||||||
|
}
|
||||||
|
} catch (com.rometools.rome.io.ParsingFeedException e) {
|
||||||
|
logger.error("Ошибка парсинга XML для Kapital.kz: {}", e.getMessage());
|
||||||
|
throw new Exception("Невалидный XML в RSS-ленте Kapital.kz: " + e.getMessage(), e);
|
||||||
|
} catch (java.net.UnknownHostException e) {
|
||||||
|
logger.error("Не удается подключиться к серверу Kapital.kz: {}", e.getMessage());
|
||||||
|
throw new Exception("Сервер Kapital.kz недоступен: " + e.getMessage(), e);
|
||||||
|
} catch (java.net.SocketTimeoutException e) {
|
||||||
|
logger.error("Таймаут при подключении к Kapital.kz: {}", e.getMessage());
|
||||||
|
throw new Exception("Таймаут подключения к Kapital.kz: " + e.getMessage(), e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Общая ошибка при получении RSS-ленты Kapital.kz: {}", e.getMessage());
|
||||||
|
throw new Exception("Ошибка получения RSS-ленты Kapital.kz: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,10 +102,31 @@ public class KursivParserService implements ParserService {
|
|||||||
* Получение RSS-ленты
|
* Получение RSS-ленты
|
||||||
*/
|
*/
|
||||||
private SyndFeed getRssFeed() throws Exception {
|
private SyndFeed getRssFeed() throws Exception {
|
||||||
URL feedUrl = URI.create(rssFeedUrl).toURL();
|
try {
|
||||||
SyndFeedInput input = new SyndFeedInput();
|
URL feedUrl = URI.create(rssFeedUrl).toURL();
|
||||||
try (XmlReader reader = new XmlReader(feedUrl.openStream())) {
|
SyndFeedInput input = new SyndFeedInput();
|
||||||
return input.build(reader);
|
|
||||||
|
// Добавляем таймаут и обработку ошибок
|
||||||
|
java.net.URLConnection connection = feedUrl.openConnection();
|
||||||
|
connection.setConnectTimeout(10000); // 10 секунд
|
||||||
|
connection.setReadTimeout(30000); // 30 секунд
|
||||||
|
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
||||||
|
|
||||||
|
try (XmlReader reader = new XmlReader(connection.getInputStream())) {
|
||||||
|
return input.build(reader);
|
||||||
|
}
|
||||||
|
} catch (com.rometools.rome.io.ParsingFeedException e) {
|
||||||
|
logger.error("Ошибка парсинга XML для Kursiv: {}", e.getMessage());
|
||||||
|
throw new Exception("Невалидный XML в RSS-ленте Kursiv: " + e.getMessage(), e);
|
||||||
|
} catch (java.net.UnknownHostException e) {
|
||||||
|
logger.error("Не удается подключиться к серверу Kursiv: {}", e.getMessage());
|
||||||
|
throw new Exception("Сервер Kursiv недоступен: " + e.getMessage(), e);
|
||||||
|
} catch (java.net.SocketTimeoutException e) {
|
||||||
|
logger.error("Таймаут при подключении к Kursiv: {}", e.getMessage());
|
||||||
|
throw new Exception("Таймаут подключения к Kursiv: " + e.getMessage(), e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Общая ошибка при получении RSS-ленты Kursiv: {}", e.getMessage());
|
||||||
|
throw new Exception("Ошибка получения RSS-ленты Kursiv: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,10 +102,31 @@ public class LsmParserService implements ParserService {
|
|||||||
* Получение RSS-ленты
|
* Получение RSS-ленты
|
||||||
*/
|
*/
|
||||||
private SyndFeed getRssFeed() throws Exception {
|
private SyndFeed getRssFeed() throws Exception {
|
||||||
URL feedUrl = URI.create(rssFeedUrl).toURL();
|
try {
|
||||||
SyndFeedInput input = new SyndFeedInput();
|
URL feedUrl = URI.create(rssFeedUrl).toURL();
|
||||||
try (XmlReader reader = new XmlReader(feedUrl.openStream())) {
|
SyndFeedInput input = new SyndFeedInput();
|
||||||
return input.build(reader);
|
|
||||||
|
// Добавляем таймаут и обработку ошибок
|
||||||
|
java.net.URLConnection connection = feedUrl.openConnection();
|
||||||
|
connection.setConnectTimeout(10000); // 10 секунд
|
||||||
|
connection.setReadTimeout(30000); // 30 секунд
|
||||||
|
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
||||||
|
|
||||||
|
try (XmlReader reader = new XmlReader(connection.getInputStream())) {
|
||||||
|
return input.build(reader);
|
||||||
|
}
|
||||||
|
} catch (com.rometools.rome.io.ParsingFeedException e) {
|
||||||
|
logger.error("Ошибка парсинга XML для LSM.kz: {}", e.getMessage());
|
||||||
|
throw new Exception("Невалидный XML в RSS-ленте LSM.kz: " + e.getMessage(), e);
|
||||||
|
} catch (java.net.UnknownHostException e) {
|
||||||
|
logger.error("Не удается подключиться к серверу LSM.kz: {}", e.getMessage());
|
||||||
|
throw new Exception("Сервер LSM.kz недоступен: " + e.getMessage(), e);
|
||||||
|
} catch (java.net.SocketTimeoutException e) {
|
||||||
|
logger.error("Таймаут при подключении к LSM.kz: {}", e.getMessage());
|
||||||
|
throw new Exception("Таймаут подключения к LSM.kz: " + e.getMessage(), e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Общая ошибка при получении RSS-ленты LSM.kz: {}", e.getMessage());
|
||||||
|
throw new Exception("Ошибка получения RSS-ленты LSM.kz: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,10 +102,31 @@ public class RbcParserService implements ParserService {
|
|||||||
* Получение RSS-ленты
|
* Получение RSS-ленты
|
||||||
*/
|
*/
|
||||||
private SyndFeed getRssFeed() throws Exception {
|
private SyndFeed getRssFeed() throws Exception {
|
||||||
URL feedUrl = URI.create(rssFeedUrl).toURL();
|
try {
|
||||||
SyndFeedInput input = new SyndFeedInput();
|
URL feedUrl = URI.create(rssFeedUrl).toURL();
|
||||||
try (XmlReader reader = new XmlReader(feedUrl.openStream())) {
|
SyndFeedInput input = new SyndFeedInput();
|
||||||
return input.build(reader);
|
|
||||||
|
// Добавляем таймаут и обработку ошибок
|
||||||
|
java.net.URLConnection connection = feedUrl.openConnection();
|
||||||
|
connection.setConnectTimeout(10000); // 10 секунд
|
||||||
|
connection.setReadTimeout(30000); // 30 секунд
|
||||||
|
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
||||||
|
|
||||||
|
try (XmlReader reader = new XmlReader(connection.getInputStream())) {
|
||||||
|
return input.build(reader);
|
||||||
|
}
|
||||||
|
} catch (com.rometools.rome.io.ParsingFeedException e) {
|
||||||
|
logger.error("Ошибка парсинга XML для РБК: {}", e.getMessage());
|
||||||
|
throw new Exception("Невалидный XML в RSS-ленте РБК: " + e.getMessage(), e);
|
||||||
|
} catch (java.net.UnknownHostException e) {
|
||||||
|
logger.error("Не удается подключиться к серверу РБК: {}", e.getMessage());
|
||||||
|
throw new Exception("Сервер РБК недоступен: " + e.getMessage(), e);
|
||||||
|
} catch (java.net.SocketTimeoutException e) {
|
||||||
|
logger.error("Таймаут при подключении к РБК: {}", e.getMessage());
|
||||||
|
throw new Exception("Таймаут подключения к РБК: " + e.getMessage(), e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Общая ошибка при получении RSS-ленты РБК: {}", e.getMessage());
|
||||||
|
throw new Exception("Ошибка получения RSS-ленты РБК: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,10 +102,31 @@ public class VedomostiParserService implements ParserService {
|
|||||||
* Получение RSS-ленты
|
* Получение RSS-ленты
|
||||||
*/
|
*/
|
||||||
private SyndFeed getRssFeed() throws Exception {
|
private SyndFeed getRssFeed() throws Exception {
|
||||||
URL feedUrl = URI.create(rssFeedUrl).toURL();
|
try {
|
||||||
SyndFeedInput input = new SyndFeedInput();
|
URL feedUrl = URI.create(rssFeedUrl).toURL();
|
||||||
try (XmlReader reader = new XmlReader(feedUrl.openStream())) {
|
SyndFeedInput input = new SyndFeedInput();
|
||||||
return input.build(reader);
|
|
||||||
|
// Добавляем таймаут и обработку ошибок
|
||||||
|
java.net.URLConnection connection = feedUrl.openConnection();
|
||||||
|
connection.setConnectTimeout(10000); // 10 секунд
|
||||||
|
connection.setReadTimeout(30000); // 30 секунд
|
||||||
|
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
||||||
|
|
||||||
|
try (XmlReader reader = new XmlReader(connection.getInputStream())) {
|
||||||
|
return input.build(reader);
|
||||||
|
}
|
||||||
|
} catch (com.rometools.rome.io.ParsingFeedException e) {
|
||||||
|
logger.error("Ошибка парсинга XML для Ведомости: {}", e.getMessage());
|
||||||
|
throw new Exception("Невалидный XML в RSS-ленте Ведомости: " + e.getMessage(), e);
|
||||||
|
} catch (java.net.UnknownHostException e) {
|
||||||
|
logger.error("Не удается подключиться к серверу Ведомости: {}", e.getMessage());
|
||||||
|
throw new Exception("Сервер Ведомости недоступен: " + e.getMessage(), e);
|
||||||
|
} catch (java.net.SocketTimeoutException e) {
|
||||||
|
logger.error("Таймаут при подключении к Ведомости: {}", e.getMessage());
|
||||||
|
throw new Exception("Таймаут подключения к Ведомости: " + e.getMessage(), e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Общая ошибка при получении RSS-ленты Ведомости: {}", e.getMessage());
|
||||||
|
throw new Exception("Ошибка получения RSS-ленты Ведомости: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package kz.konturai.parser.util;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Утилита для проверки доступности RSS-лент
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class RssUrlValidator {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(RssUrlValidator.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет доступность RSS-ленты
|
||||||
|
*/
|
||||||
|
public boolean isRssUrlAccessible(String url) {
|
||||||
|
try {
|
||||||
|
URL feedUrl = URI.create(url).toURL();
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) feedUrl.openConnection();
|
||||||
|
connection.setRequestMethod("HEAD");
|
||||||
|
connection.setConnectTimeout(10000);
|
||||||
|
connection.setReadTimeout(10000);
|
||||||
|
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
||||||
|
|
||||||
|
int responseCode = connection.getResponseCode();
|
||||||
|
connection.disconnect();
|
||||||
|
|
||||||
|
return responseCode == 200;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("RSS URL недоступен: {} - {}", url, e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает альтернативные URL для RSS-лент
|
||||||
|
*/
|
||||||
|
public Map<String, String[]> getAlternativeUrls() {
|
||||||
|
Map<String, String[]> alternatives = new HashMap<>();
|
||||||
|
|
||||||
|
// Альтернативные URL для Kapital.kz
|
||||||
|
alternatives.put("kapital", new String[] {
|
||||||
|
"https://kapital.kz/rss/",
|
||||||
|
"https://kapital.kz/feed/",
|
||||||
|
"https://kapital.kz/rss.xml"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Альтернативные URL для Kursiv
|
||||||
|
alternatives.put("kursiv", new String[] {
|
||||||
|
"https://kursiv.media/feed/",
|
||||||
|
"https://kursiv.media/rss/",
|
||||||
|
"https://kursiv.media/rss.xml"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Альтернативные URL для LSM.kz
|
||||||
|
alternatives.put("lsm", new String[] {
|
||||||
|
"https://lsm.kz/rss",
|
||||||
|
"https://lsm.kz/feed/",
|
||||||
|
"https://lsm.kz/rss.xml"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Альтернативные URL для РБК
|
||||||
|
alternatives.put("rbc", new String[] {
|
||||||
|
"https://static.feed.rbc.ru/rbc/logical/footer/news.rss",
|
||||||
|
"https://rbc.ru/rss/",
|
||||||
|
"https://rbc.ru/feed/",
|
||||||
|
"https://www.rbc.ru/rss/",
|
||||||
|
"https://www.rbc.ru/feed/"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Альтернативные URL для Ведомости
|
||||||
|
alternatives.put("vedomosti", new String[] {
|
||||||
|
"https://www.vedomosti.ru/rss/rubric/technology/internet",
|
||||||
|
"https://www.vedomosti.ru/rss/",
|
||||||
|
"https://www.vedomosti.ru/feed/",
|
||||||
|
"https://vedomosti.ru/rss/"
|
||||||
|
});
|
||||||
|
|
||||||
|
return alternatives;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Находит первый доступный URL из списка альтернатив
|
||||||
|
*/
|
||||||
|
public String findWorkingUrl(String sourceName) {
|
||||||
|
Map<String, String[]> alternatives = getAlternativeUrls();
|
||||||
|
String[] urls = alternatives.get(sourceName);
|
||||||
|
|
||||||
|
if (urls == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String url : urls) {
|
||||||
|
if (isRssUrlAccessible(url)) {
|
||||||
|
logger.info("Найден рабочий URL для {}: {}", sourceName, url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.warn("Не найдено рабочих URL для источника: {}", sourceName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет все RSS-ленты и возвращает отчет
|
||||||
|
*/
|
||||||
|
public Map<String, Boolean> checkAllRssUrls(Map<String, String> rssUrls) {
|
||||||
|
Map<String, Boolean> results = new HashMap<>();
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> entry : rssUrls.entrySet()) {
|
||||||
|
String source = entry.getKey();
|
||||||
|
String url = entry.getValue();
|
||||||
|
|
||||||
|
boolean isAccessible = isRssUrlAccessible(url);
|
||||||
|
results.put(source, isAccessible);
|
||||||
|
|
||||||
|
if (!isAccessible) {
|
||||||
|
logger.warn("RSS URL недоступен: {} - {}", source, url);
|
||||||
|
String alternativeUrl = findWorkingUrl(source);
|
||||||
|
if (alternativeUrl != null) {
|
||||||
|
logger.info("Рекомендуется использовать альтернативный URL для {}: {}", source, alternativeUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package kz.konturai.parser.controller;
|
||||||
|
|
||||||
|
import kz.konturai.parser.dto.ApiResponse;
|
||||||
|
import kz.konturai.parser.service.MarketItemService;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class SourceNamesEndpointTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private MarketItemService marketItemService;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private MarketItemController marketItemController;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
// Setup common test data
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetSources_Success() {
|
||||||
|
// Given
|
||||||
|
List<String> mockSources = List.of(
|
||||||
|
"Kursiv (Бизнес/экономика)",
|
||||||
|
"Kapital.kz (Бизнес)",
|
||||||
|
"LSM.kz",
|
||||||
|
"РБК",
|
||||||
|
"Ведомости");
|
||||||
|
when(marketItemService.getAllSourceNames()).thenReturn(mockSources);
|
||||||
|
|
||||||
|
// When
|
||||||
|
ResponseEntity<ApiResponse<List<String>>> response = marketItemController.getSources();
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||||
|
assertNotNull(response.getBody());
|
||||||
|
assertTrue(response.getBody().isSuccess());
|
||||||
|
assertEquals(5, response.getBody().getData().size());
|
||||||
|
assertTrue(response.getBody().getData().contains("Kursiv (Бизнес/экономика)"));
|
||||||
|
assertTrue(response.getBody().getData().contains("Kapital.kz (Бизнес)"));
|
||||||
|
assertTrue(response.getBody().getData().contains("LSM.kz"));
|
||||||
|
assertTrue(response.getBody().getData().contains("РБК"));
|
||||||
|
assertTrue(response.getBody().getData().contains("Ведомости"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetSources_EmptyList() {
|
||||||
|
// Given
|
||||||
|
when(marketItemService.getAllSourceNames()).thenReturn(List.of());
|
||||||
|
|
||||||
|
// When
|
||||||
|
ResponseEntity<ApiResponse<List<String>>> response = marketItemController.getSources();
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||||
|
assertNotNull(response.getBody());
|
||||||
|
assertTrue(response.getBody().isSuccess());
|
||||||
|
assertTrue(response.getBody().getData().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetSources_ServiceError() {
|
||||||
|
// Given
|
||||||
|
when(marketItemService.getAllSourceNames()).thenThrow(new RuntimeException("Database error"));
|
||||||
|
|
||||||
|
// When
|
||||||
|
ResponseEntity<ApiResponse<List<String>>> response = marketItemController.getSources();
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
|
||||||
|
assertNotNull(response.getBody());
|
||||||
|
assertFalse(response.getBody().isSuccess());
|
||||||
|
assertTrue(response.getBody().getMessage().contains("Ошибка при получении источников"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user