From 3c16839fa59ee5696c4eb76227e14ff33964fe2c Mon Sep 17 00:00:00 2001 From: "mula.liu" Date: Sat, 27 Dec 2025 17:38:20 +0800 Subject: [PATCH] 1.0.3 --- .DS_Store | Bin 14340 -> 14340 bytes .gemini-clipboard/clipboard-1765391166898.png | Bin 18633 -> 0 bytes .gemini-clipboard/clipboard-1765392058631.png | Bin 81755 -> 0 bytes backend/scripts/UPGRADE_GUIDE.md | 177 ++++++++++ .../add_nasa_horizons_cron_source.sql | 0 .../add_predefined_jobs_support.sql | 0 backend/scripts/upgrade_production.sql | 144 ++++++++ backend/test_nasa_body_param.py | 42 --- backend/test_nasa_sbdb.py | 51 --- backend/test_phase5.py | 307 ------------------ 10 files changed, 321 insertions(+), 400 deletions(-) delete mode 100644 .gemini-clipboard/clipboard-1765391166898.png delete mode 100644 .gemini-clipboard/clipboard-1765392058631.png create mode 100644 backend/scripts/UPGRADE_GUIDE.md rename backend/{migrations => scripts}/add_nasa_horizons_cron_source.sql (100%) rename backend/{migrations => scripts}/add_predefined_jobs_support.sql (100%) create mode 100644 backend/scripts/upgrade_production.sql delete mode 100644 backend/test_nasa_body_param.py delete mode 100644 backend/test_nasa_sbdb.py delete mode 100644 backend/test_phase5.py diff --git a/.DS_Store b/.DS_Store index c6c7610a1a4fc62af74b8ae73d1d7d8c79725299..5d83e988a5d8330cdd4eefb5f93b99c4a0be119a 100644 GIT binary patch delta 231 zcmZoEXepQ=&Db_k##q#hfq{XUfkA+QA(J7Gp`t9fC@&{}vLK`KWCIfxRz{#K%VbVL zX-R*Ec!o5FB8EhUT!vJJVg@~iDxi)6&z#MIjNjNMD=_+QX6InxV3h#skeXaAs65$A z@Y&=&LOh#$gbp!HUMeQId7`KZ<7Q#;9~_$%7@spT%5Dx(_{co@v%lZDj{ zCikn)o;bmKvbg~7WGR97o7JSAvH}%9W}M7!Ai~Hy1ni|6Z-DTR&)mdyZ{nF9c(y* zU78#2D1lbi=NG8y!?CfCik=$2dPF7$d;uYjIdkF;G!~MA!sZfGp$0i9LWBHuIHu76 z2g-EHQ?CpMP@*%{u%$%tO_o?pe(}uDO@(wFH1Szc@xzeWyW$AuA{wW{v#H{cjYHVjS~po(|Mae{{<3i{ zo;jl|lej`P8}H>bJ}k&=lCV6QL=V%PW)#WDad_q)8IMpmlbA8Ar21D3DQDm_0u@^c z8BdLyVCV8W-|k$r=?hZm##DB$K8h$>5q}>*4-Q9PUJPe50-&9J!dvr>eQ#_F{{nOM E1DI-Yi2wiq diff --git a/.gemini-clipboard/clipboard-1765391166898.png b/.gemini-clipboard/clipboard-1765391166898.png deleted file mode 100644 index 9486ce2f58bfa59c66d0599dc1c189d814b7b40b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18633 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 diff --git a/.gemini-clipboard/clipboard-1765392058631.png b/.gemini-clipboard/clipboard-1765392058631.png deleted file mode 100644 index f7229d73e7c10ee9218a23308bdfee46b9038b2a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81755 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 Koupe&Uj9Gq0kMt% diff --git a/backend/scripts/UPGRADE_GUIDE.md b/backend/scripts/UPGRADE_GUIDE.md new file mode 100644 index 0000000..c5e82bf --- /dev/null +++ b/backend/scripts/UPGRADE_GUIDE.md @@ -0,0 +1,177 @@ +# 生产环境数据库升级指南 + +## 概述 +此升级脚本包含以下变更: +1. 在 `celestial_bodies` 表增加 `short_name` 字段 +2. 完整导入 `menus` 和 `role_menus` 表 +3. 清空 `celestial_events` 表(将由定时任务重新生成) +4. 完整导入 `scheduled_jobs` 表 +5. 导入/更新 `system_settings` 表 +6. 保留 `user_follows` 表的现有数据 + +## 升级前准备 + +### 1. 备份数据库 +```bash +# 在生产服务器上执行 +pg_dump -U postgres -d cosmo_db > backup_$(date +%Y%m%d_%H%M%S).sql +``` + +### 2. 测试升级脚本(推荐) +```bash +# 在测试环境先运行 +psql -U postgres -d cosmo_db_test < upgrade_production.sql +``` + +## 执行升级 + +### 方式1:直接执行SQL文件 +```bash +psql -U postgres -d cosmo_db < upgrade_production.sql +``` + +### 方式2:通过Docker容器执行 +```bash +docker cp upgrade_production.sql :/tmp/ +docker exec -it psql -U postgres -d cosmo_db -f /tmp/upgrade_production.sql +``` + +### 方式3:交互式执行(推荐,便于观察) +```bash +psql -U postgres -d cosmo_db +\i upgrade_production.sql +``` + +## 升级后验证 + +脚本会自动输出验证信息,检查以下内容: + +1. **celestial_bodies.short_name 字段**:应该存在 +2. **menus 数量**:应该是 14 条 +3. **role_menus 数量**:应该是 16 条 +4. **scheduled_jobs 数量**:应该是 2 条 +5. **system_settings 数量**:应该至少 3 条 + +### 手动验证命令 +```sql +-- 检查 short_name 字段 +\d celestial_bodies + +-- 检查菜单数据 +SELECT id, name, title, path FROM menus ORDER BY parent_id NULLS FIRST, sort_order; + +-- 检查角色菜单关联 +SELECT r.name as role, m.title as menu +FROM role_menus rm +JOIN roles r ON rm.role_id = r.id +JOIN menus m ON rm.menu_id = m.id +ORDER BY r.name, m.sort_order; + +-- 检查定时任务 +SELECT id, name, is_active, predefined_function FROM scheduled_jobs; + +-- 检查系统设置 +SELECT key, value, value_type FROM system_settings; +``` + +## 升级详情 + +### 1. celestial_bodies 表升级 +- 增加 `short_name VARCHAR(50)` 字段 +- 如果字段已存在,则跳过 + +### 2. menus 和 role_menus 导入 +- **重要**:会清空现有菜单数据 +- 导入 14 条菜单记录 +- 导入 16 条角色-菜单关联记录 +- 管理员可访问所有菜单 +- 普通用户只能访问:个人资料、我的天体 + +### 3. celestial_events 清空 +- 清空所有现有天体事件 +- 数据会由定时任务 `calculate_planetary_events` 自动重新生成 + +### 4. scheduled_jobs 导入 +导入2个定时任务: +- **每日更新天体位置数据**(已禁用) + - Cron: `0 2 * * *`(每天凌晨2点) + - 可通过后台管理界面手动执行 + +- **获取主要天体事件**(已启用) + - Cron: `0 3 1 * *`(每月1日凌晨3点) + - 自动计算未来一年的天文事件 + +### 5. system_settings 导入 +导入3个系统设置: +- `view_mode`: solar(默认视图模式) +- `nasa_api_timeout`: 120(NASA API超时时间) +- `auto_download_positions`: False(自动下载位置数据开关) + +使用 `ON CONFLICT` 策略,如果键已存在则更新值。 + +### 6. user_follows 保留 +- **不会修改此表** +- 保留所有用户关注数据 + +## 回滚方案 + +如果升级失败,使用备份恢复: + +```bash +# 方式1:完整恢复 +psql -U postgres -d cosmo_db < backup_YYYYMMDD_HHMMSS.sql + +# 方式2:选择性回滚 +# 如果只是某些表有问题,可以只恢复特定表 +pg_restore -U postgres -d cosmo_db -t menus -t role_menus backup.dump +``` + +## 注意事项 + +1. **事务安全**:整个脚本在一个事务中执行,失败会自动回滚 +2. **外键约束**:menus 表有自引用外键,脚本已处理 +3. **数据清空**:menus、role_menus、celestial_events、scheduled_jobs 会被清空 +4. **用户数据**:user_follows 不会被修改 +5. **定时任务**:位置数据下载任务默认禁用,需要手动执行或启用 + +## 升级后操作 + +1. **重启应用服务** + ```bash + # 重启后端服务 + systemctl restart cosmo-backend + # 或 docker restart cosmo-backend + ``` + +2. **手动执行位置数据下载**(如需要) + - 登录后台管理系统 + - 进入"定时任务设置" + - 找到"每日更新天体位置数据" + - 点击"立即执行" + +3. **验证前端功能** + - 登录系统 + - 检查菜单是否正确显示 + - 测试个人资料页面 + - 测试我的天体页面 + +## 常见问题 + +### Q: 升级过程中断怎么办? +A: 由于使用了事务,中断会自动回滚。使用备份重新开始。 + +### Q: 如何只导入某个表? +A: 从脚本中复制对应表的部分,单独执行。 + +### Q: 线上已有自定义菜单怎么办? +A: 脚本会清空菜单,请在升级前导出自定义菜单,升级后手动添加。 + +### Q: 定时任务什么时候开始执行? +A: 天体事件任务会在下个月1日凌晨3点执行。位置数据任务需手动启用或执行。 + +## 联系支持 + +如遇问题,请检查: +1. 数据库日志 +2. 应用程序日志 +3. 脚本执行输出 diff --git a/backend/migrations/add_nasa_horizons_cron_source.sql b/backend/scripts/add_nasa_horizons_cron_source.sql similarity index 100% rename from backend/migrations/add_nasa_horizons_cron_source.sql rename to backend/scripts/add_nasa_horizons_cron_source.sql diff --git a/backend/migrations/add_predefined_jobs_support.sql b/backend/scripts/add_predefined_jobs_support.sql similarity index 100% rename from backend/migrations/add_predefined_jobs_support.sql rename to backend/scripts/add_predefined_jobs_support.sql diff --git a/backend/scripts/upgrade_production.sql b/backend/scripts/upgrade_production.sql new file mode 100644 index 0000000..474825f --- /dev/null +++ b/backend/scripts/upgrade_production.sql @@ -0,0 +1,144 @@ +-- ============================================================ +-- Production Database Upgrade Script +-- ============================================================ +-- This script upgrades the production database with the following changes: +-- 1. Add short_name to celestial_bodies +-- 2. Import menus and role_menus +-- 3. Import celestial_events +-- 4. Import scheduled_jobs +-- 5. Import system_settings +-- 6. Import user_follows +-- +-- IMPORTANT: Run this script in a transaction and test on a backup first! +-- ============================================================ + +BEGIN; + +-- ============================================================ +-- 1. Add short_name column to celestial_bodies +-- ============================================================ +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'celestial_bodies' + AND column_name = 'short_name' + ) THEN + ALTER TABLE celestial_bodies ADD COLUMN short_name VARCHAR(50); + RAISE NOTICE 'Added short_name column to celestial_bodies'; + ELSE + RAISE NOTICE 'short_name column already exists'; + END IF; +END $$; + +-- ============================================================ +-- 2. Import menus and role_menus +-- ============================================================ + +-- Clear existing menus (will cascade to role_menus due to foreign key) +TRUNCATE TABLE menus CASCADE; +RAISE NOTICE 'Cleared existing menus and role_menus'; + +-- Disable triggers temporarily to handle circular foreign keys +ALTER TABLE menus DISABLE TRIGGER ALL; + +-- Insert menus (parent menus first, then child menus) +INSERT INTO menus (id, parent_id, name, title, icon, path, component, sort_order, is_active, description, created_at, updated_at) VALUES +(1, NULL, 'dashboard', '控制台', 'dashboard', '/admin/dashboard', 'admin/Dashboard', 1, true, '系统控制台', '2025-11-28 18:07:11.767382', '2025-11-28 18:07:11.767382'), +(2, NULL, 'data_management', '数据管理', 'database', '', '', 2, true, '数据管理模块', '2025-11-28 18:07:11.767382', '2025-11-28 18:07:11.767382'), +(6, NULL, 'platform_management', '平台管理', 'settings', '', '', 3, true, '管理用户和系统参数', '2025-11-29 19:03:08.776597', '2025-11-29 19:03:08.776597'), +(14, NULL, 'user_profile', '个人资料', 'profile', '/user/profile', 'user/Profile', 1, true, '个人资料管理', '2025-12-18 16:26:11.778475', '2025-12-18 16:26:11.778475'), +(15, NULL, 'user_follow', '我的天体', 'star', '/user/follow', 'user/UserFollow', 2, true, '我关注的天体', '2025-12-18 16:27:48.688747', '2025-12-18 16:27:48.688747'), +(11, 2, 'star_systems', '恒星系统管理', 'StarOutlined', '/admin/star-systems', 'StarSystems', 1, true, '管理太阳系和系外恒星系统', '2025-12-06 02:35:21.137234', '2025-12-06 02:35:21.137234'), +(3, 2, 'celestial_bodies', '天体数据管理', NULL, '/admin/celestial-bodies', 'admin/CelestialBodies', 2, true, '查看和管理天体数据', '2025-11-28 18:07:11.767382', '2025-11-28 18:07:11.767382'), +(4, 2, 'static_data', '静态数据管理', NULL, '/admin/static-data', 'admin/StaticData', 2, true, '查看和管理静态数据(星座、星系等)', '2025-11-28 18:07:11.767382', '2025-11-28 18:07:11.767382'), +(5, 2, 'nasa_data', 'Horizon数据下载', NULL, '/admin/nasa-data', 'admin/NasaData', 3, true, '管理NASA Horizons数据下载', '2025-11-28 18:07:11.767382', '2025-11-28 18:07:11.767382'), +(13, 2, 'celestial_events', '天体事件', 'CalendarOutlined', '/admin/celestial-events', '', 4, true, '', '2025-12-15 03:20:39.798021', '2025-12-15 03:20:39.798021'), +(7, 6, 'user_management', '用户管理', NULL, '/admin/users', 'admin/Users', 1, true, '管理系统用户账号', '2025-11-29 19:03:08.776597', '2025-11-29 19:03:08.776597'), +(8, 6, 'platform_parameters_management', '平台参数管理', NULL, '/admin/settings', 'admin/Settings', 2, true, '管理系统通用配置参数', '2025-11-29 19:03:08.776597', '2025-11-29 19:03:08.776597'), +(12, 6, 'scheduled_jobs', '定时任务设置', 'ClockCircleOutlined', '/admin/scheduled-jobs', 'admin/ScheduledJobs', 5, true, '管理系统定时任务及脚本', '2025-12-10 17:42:38.031518', '2025-12-10 17:42:38.031518'), +(10, 6, 'system_tasks', '系统任务监控', 'schedule', '/admin/tasks', 'admin/Tasks', 30, true, '', '2025-11-30 16:04:59.572869', '2025-11-30 16:04:59.572869'); + +-- Re-enable triggers +ALTER TABLE menus ENABLE TRIGGER ALL; +RAISE NOTICE 'Imported menus data'; + +-- Reset sequence for menus +SELECT setval('menus_id_seq', (SELECT MAX(id) FROM menus)); + +-- Insert role_menus +INSERT INTO role_menus (role_id, menu_id) VALUES +-- Admin role (role_id = 1) has access to all menus +(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 10), (1, 11), (1, 12), (1, 13), (1, 14), (1, 15), +-- User role (role_id = 2) has access to user menus only +(2, 14), (2, 15); + +RAISE NOTICE 'Imported role_menus data'; + +-- ============================================================ +-- 3. Import celestial_events (will be truncated and re-imported) +-- ============================================================ +TRUNCATE TABLE celestial_events; +RAISE NOTICE 'Cleared existing celestial_events (data will be regenerated by scheduled jobs)'; + +-- ============================================================ +-- 4. Import scheduled_jobs +-- ============================================================ +-- Clear existing scheduled_jobs +TRUNCATE TABLE scheduled_jobs CASCADE; +RAISE NOTICE 'Cleared existing scheduled_jobs'; + +-- Insert scheduled_jobs +INSERT INTO scheduled_jobs (id, name, cron_expression, python_code, is_active, last_run_at, last_run_status, next_run_at, description, created_at, updated_at, job_type, predefined_function, function_params) VALUES +(1, '每日更新天体位置数据', '0 2 * * *', NULL, false, NULL, NULL, NULL, '每天凌晨2点自动从NASA Horizons下载主要天体的位置数据', '2025-12-10 17:43:01.234567', '2025-12-10 17:43:01.234567', 'predefined', 'download_positions_task', '{"body_ids": ["10", "199", "299", "399", "301", "499", "599", "699", "799", "899"], "days_range": "3"}'), +(2, '获取主要天体的食、合、冲等事件', '0 3 1 * *', NULL, true, NULL, NULL, NULL, '每月1日凌晨3点计算未来一年的主要天文事件', '2025-12-10 17:43:01.234567', '2025-12-10 17:43:01.234567', 'predefined', 'calculate_planetary_events', '{"body_ids": ["199", "299", "499", "599", "699", "799", "899"], "days_ahead": "365", "clean_old_events": true, "threshold_degrees": "5", "calculate_close_approaches": true}'); + +-- Reset sequence +SELECT setval('scheduled_jobs_id_seq', (SELECT MAX(id) FROM scheduled_jobs)); +RAISE NOTICE 'Imported scheduled_jobs data'; + +-- ============================================================ +-- 5. Import system_settings +-- ============================================================ +-- Use INSERT ... ON CONFLICT to avoid duplicates +INSERT INTO system_settings (key, value, value_type, category, label, description, is_public, created_at, updated_at) VALUES +('view_mode', 'solar', 'string', 'ui', '默认视图模式', '系统默认的3D场景视图模式(solar或galaxy)', true, NOW(), NOW()), +('nasa_api_timeout', '120', 'int', 'api', 'NASA API超时时间', 'NASA Horizons API请求超时时间(秒)', false, NOW(), NOW()), +('auto_download_positions', 'False', 'bool', 'system', '自动下载位置数据', '当位置数据不存在时是否自动从NASA Horizons下载', false, NOW(), NOW()) +ON CONFLICT (key) DO UPDATE SET + value = EXCLUDED.value, + value_type = EXCLUDED.value_type, + category = EXCLUDED.category, + label = EXCLUDED.label, + description = EXCLUDED.description, + is_public = EXCLUDED.is_public, + updated_at = NOW(); + +RAISE NOTICE 'Imported/updated system_settings data'; + +-- ============================================================ +-- 6. Import user_follows (keep existing data, don't truncate) +-- ============================================================ +-- Note: user_follows should retain existing production data +-- This section is intentionally left empty to preserve user data +RAISE NOTICE 'Skipped user_follows import (preserving existing user data)'; + +-- ============================================================ +-- Commit transaction +-- ============================================================ +COMMIT; + +-- ============================================================ +-- Verification queries +-- ============================================================ +\echo '============================================================' +\echo 'Upgrade completed successfully!' +\echo '============================================================' +\echo 'Verification:' +SELECT 'celestial_bodies.short_name exists:' as check, + EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name='celestial_bodies' AND column_name='short_name') as result; +SELECT 'menus count:' as check, COUNT(*) as result FROM menus; +SELECT 'role_menus count:' as check, COUNT(*) as result FROM role_menus; +SELECT 'scheduled_jobs count:' as check, COUNT(*) as result FROM scheduled_jobs; +SELECT 'system_settings count:' as check, COUNT(*) as result FROM system_settings; +\echo '============================================================' diff --git a/backend/test_nasa_body_param.py b/backend/test_nasa_body_param.py deleted file mode 100644 index f735400..0000000 --- a/backend/test_nasa_body_param.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -Test NASA SBDB API body parameter format -""" -import asyncio -import httpx - -async def test_body_param(): - """Test different body parameter formats""" - - test_cases = [ - ("Earth (name)", "Earth"), - ("399 (Horizons ID)", "399"), - ("Mars (name)", "Mars"), - ("499 (Mars Horizons ID)", "499"), - ] - - for name, body_value in test_cases: - params = { - "date-min": "2025-12-15", - "date-max": "2025-12-16", - "body": body_value, - "limit": "1" - } - - try: - async with httpx.AsyncClient(timeout=10.0, proxies={}) as client: - response = await client.get( - "https://ssd-api.jpl.nasa.gov/cad.api", - params=params - ) - - if response.status_code == 200: - data = response.json() - count = data.get("count", 0) - print(f"{name:30} -> 返回 {count:3} 个结果 ✓") - else: - print(f"{name:30} -> HTTP {response.status_code} ✗") - except Exception as e: - print(f"{name:30} -> 错误: {e}") - -if __name__ == "__main__": - asyncio.run(test_body_param()) diff --git a/backend/test_nasa_sbdb.py b/backend/test_nasa_sbdb.py deleted file mode 100644 index 0c0dc4b..0000000 --- a/backend/test_nasa_sbdb.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -Test NASA SBDB service directly -""" -import asyncio -from datetime import datetime, timedelta -from app.services.nasa_sbdb_service import nasa_sbdb_service - -async def test_nasa_sbdb(): - """Test NASA SBDB API directly""" - - # Calculate date range - date_min = datetime.utcnow().strftime("%Y-%m-%d") - date_max = (datetime.utcnow() + timedelta(days=365)).strftime("%Y-%m-%d") - - print(f"Querying NASA SBDB for close approaches...") - print(f"Date range: {date_min} to {date_max}") - print(f"Max distance: 1.0 AU") - - events = await nasa_sbdb_service.get_close_approaches( - date_min=date_min, - date_max=date_max, - dist_max="1.0", - body="Earth", - limit=10, - fullname=True - ) - - print(f"\nRetrieved {len(events)} events from NASA SBDB") - - if events: - print("\nFirst 3 events:") - for i, event in enumerate(events[:3], 1): - print(f"\n{i}. {event.get('des', 'Unknown')}") - print(f" Full name: {event.get('fullname', 'N/A')}") - print(f" Date: {event.get('cd', 'N/A')}") - print(f" Distance: {event.get('dist', 'N/A')} AU") - print(f" Velocity: {event.get('v_rel', 'N/A')} km/s") - - # Test parsing - parsed = nasa_sbdb_service.parse_event_to_celestial_event(event) - if parsed: - print(f" ✓ Parsed successfully") - print(f" Title: {parsed['title']}") - print(f" Body ID: {parsed['body_id']}") - else: - print(f" ✗ Failed to parse") - else: - print("No events found") - -if __name__ == "__main__": - asyncio.run(test_nasa_sbdb()) diff --git a/backend/test_phase5.py b/backend/test_phase5.py deleted file mode 100644 index 3e01809..0000000 --- a/backend/test_phase5.py +++ /dev/null @@ -1,307 +0,0 @@ -""" -Test script for Phase 5 features -Tests social features (follows, channel messages) and event system -""" -import asyncio -import httpx -import json -from datetime import datetime - -BASE_URL = "http://localhost:8000/api" - -# Test user credentials (assuming these exist from previous tests) -TEST_USER = { - "username": "testuser", - "password": "testpass123" -} - -async def get_auth_token(): - """Login and get JWT token""" - async with httpx.AsyncClient(timeout=30.0, proxies={}) as client: - # Try to register first (in case user doesn't exist) - register_response = await client.post( - f"{BASE_URL}/auth/register", - json={ - "username": TEST_USER["username"], - "password": TEST_USER["password"], - "email": "test@example.com" - } - ) - - # If register fails (user exists), try to login - if register_response.status_code != 200: - response = await client.post( - f"{BASE_URL}/auth/login", - json={ - "username": TEST_USER["username"], - "password": TEST_USER["password"] - } - ) - else: - response = register_response - - if response.status_code == 200: - data = response.json() - return data.get("access_token") - else: - print(f"Login failed: {response.status_code} - {response.text}") - return None - -async def test_follow_operations(token): - """Test user follow operations""" - print("\n=== Testing Follow Operations ===") - headers = {"Authorization": f"Bearer {token}"} - - async with httpx.AsyncClient(timeout=30.0, proxies={}) as client: - # Test: Follow a celestial body (Mars) - print("\n1. Following Mars (499)...") - response = await client.post( - f"{BASE_URL}/social/follow/499", - headers=headers - ) - print(f"Status: {response.status_code}") - if response.status_code in [200, 400]: # 400 if already following - print(f"Response: {response.json()}") - - # Test: Get user's follows - print("\n2. Getting user follows...") - response = await client.get( - f"{BASE_URL}/social/follows", - headers=headers - ) - print(f"Status: {response.status_code}") - if response.status_code == 200: - follows = response.json() - print(f"Following {len(follows)} bodies:") - for follow in follows[:5]: # Show first 5 - print(f" - Body ID: {follow['body_id']}, Since: {follow['created_at']}") - - # Test: Check if following Mars - print("\n3. Checking if following Mars...") - response = await client.get( - f"{BASE_URL}/social/follows/check/499", - headers=headers - ) - print(f"Status: {response.status_code}") - if response.status_code == 200: - print(f"Response: {response.json()}") - - return response.status_code == 200 - -async def test_channel_messages(token): - """Test channel message operations""" - print("\n=== Testing Channel Messages ===") - headers = {"Authorization": f"Bearer {token}"} - - async with httpx.AsyncClient(timeout=30.0, proxies={}) as client: - # Test: Post a message to Mars channel - print("\n1. Posting message to Mars channel...") - message_data = { - "content": f"Test message at {datetime.now().isoformat()}" - } - response = await client.post( - f"{BASE_URL}/social/channel/499/message", - headers=headers, - json=message_data - ) - print(f"Status: {response.status_code}") - if response.status_code == 200: - print(f"Response: {response.json()}") - elif response.status_code == 403: - print("Error: User is not following this body (need to follow first)") - - # Test: Get channel messages - print("\n2. Getting Mars channel messages...") - response = await client.get( - f"{BASE_URL}/social/channel/499/messages?limit=10", - headers=headers - ) - print(f"Status: {response.status_code}") - if response.status_code == 200: - messages = response.json() - print(f"Found {len(messages)} messages:") - for msg in messages[-3:]: # Show last 3 - print(f" - {msg['username']}: {msg['content'][:50]}...") - - return response.status_code == 200 - -async def test_celestial_events(token): - """Test celestial event operations""" - print("\n=== Testing Celestial Events ===") - headers = {"Authorization": f"Bearer {token}"} - - async with httpx.AsyncClient(timeout=30.0, proxies={}) as client: - # Test: Get upcoming events - print("\n1. Getting upcoming celestial events...") - response = await client.get( - f"{BASE_URL}/events?limit=10", - headers=headers - ) - print(f"Status: {response.status_code}") - if response.status_code == 200: - events = response.json() - print(f"Found {len(events)} events:") - for event in events[:5]: # Show first 5 - print(f" - {event['title']} at {event['event_time']}") - print(f" Type: {event['event_type']}, Source: {event['source']}") - - # Test: Get events for a specific body - print("\n2. Getting events for Mars (499)...") - response = await client.get( - f"{BASE_URL}/events?body_id=499&limit=5", - headers=headers - ) - print(f"Status: {response.status_code}") - if response.status_code == 200: - events = response.json() - print(f"Found {len(events)} events for Mars") - - return response.status_code == 200 - -async def test_scheduled_tasks(token): - """Test scheduled task functionality""" - print("\n=== Testing Scheduled Tasks ===") - headers = {"Authorization": f"Bearer {token}"} - - async with httpx.AsyncClient(timeout=120.0, proxies={}) as client: - # Test: Get available tasks - print("\n1. Getting available scheduled tasks...") - response = await client.get( - f"{BASE_URL}/scheduled-jobs/available-tasks", - headers=headers - ) - print(f"Status: {response.status_code}") - if response.status_code == 200: - tasks = response.json() - print(f"Found {len(tasks)} available tasks") - - # Find our Phase 5 task - phase5_task = None - for task in tasks: - if task['name'] == 'fetch_close_approach_events': - phase5_task = task - print(f"\nFound Phase 5 task: {task['name']}") - print(f" Description: {task['description']}") - print(f" Category: {task['category']}") - break - - if phase5_task: - # Test: Create a scheduled job for this task - print("\n2. Creating a scheduled job for fetch_close_approach_events...") - job_data = { - "name": "Test Phase 5 Close Approach Events", - "job_type": "predefined", - "predefined_function": "fetch_close_approach_events", - "function_params": { - "days_ahead": 30, - "dist_max": "0.2", - "approach_body": "Earth", - "limit": 50, - "clean_old_events": False - }, - "cron_expression": "0 0 * * *", # Daily at midnight - "description": "Test job for Phase 5", - "is_active": False # Don't activate for test - } - - response = await client.post( - f"{BASE_URL}/scheduled-jobs", - headers=headers, - json=job_data - ) - print(f"Status: {response.status_code}") - - if response.status_code == 201: - job = response.json() - job_id = job['id'] - print(f"Created job with ID: {job_id}") - - # Test: Run the job immediately - print(f"\n3. Triggering job {job_id} to run now...") - print(" (This may take 30-60 seconds...)") - response = await client.post( - f"{BASE_URL}/scheduled-jobs/{job_id}/run", - headers=headers - ) - print(f"Status: {response.status_code}") - if response.status_code == 200: - print(f"Response: {response.json()}") - - # Wait a bit and check job status - print("\n4. Waiting 60 seconds for job to complete...") - await asyncio.sleep(60) - - # Get job status - response = await client.get( - f"{BASE_URL}/scheduled-jobs/{job_id}", - headers=headers - ) - if response.status_code == 200: - job_status = response.json() - print(f"Job status: {job_status.get('last_run_status')}") - print(f"Last run at: {job_status.get('last_run_at')}") - - # Check if events were created - response = await client.get( - f"{BASE_URL}/events?limit=10", - headers=headers - ) - if response.status_code == 200: - events = response.json() - print(f"\nEvents in database: {len(events)}") - for event in events[:3]: - print(f" - {event['title']}") - - # Clean up: delete the test job - await client.delete( - f"{BASE_URL}/scheduled-jobs/{job_id}", - headers=headers - ) - print(f"\nCleaned up test job {job_id}") - - return True - else: - print(f"Error triggering job: {response.text}") - else: - print(f"Error creating job: {response.text}") - - return False - -async def main(): - """Main test function""" - print("=" * 60) - print("Phase 5 Feature Testing") - print("=" * 60) - - # Get authentication token - print("\nAuthenticating...") - token = await get_auth_token() - if not token: - print("ERROR: Failed to authenticate. Please ensure test user exists.") - print("You may need to create a test user first.") - return - - print(f"✓ Authentication successful") - - # Run tests - results = { - "follow_operations": await test_follow_operations(token), - "channel_messages": await test_channel_messages(token), - "celestial_events": await test_celestial_events(token), - "scheduled_tasks": await test_scheduled_tasks(token) - } - - # Summary - print("\n" + "=" * 60) - print("Test Summary") - print("=" * 60) - for test_name, passed in results.items(): - status = "✓ PASS" if passed else "✗ FAIL" - print(f"{status} - {test_name}") - - total_passed = sum(results.values()) - total_tests = len(results) - print(f"\nTotal: {total_passed}/{total_tests} tests passed") - -if __name__ == "__main__": - asyncio.run(main())