项目文件夹

文件
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

747 行
25 KiB
Python

"""
Comprehensive tests for BackgroundJobScheduler class.
Tests scheduling, user sessions, document processing, and configuration.
"""
import pytest
from unittest.mock import MagicMock, Mock, patch
from datetime import datetime
import threading
class TestNewsSchedulerSingleton:
"""Tests for BackgroundJobScheduler singleton pattern."""
def test_returns_same_instance(self):
"""Test returns same instance on multiple calls."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
# Reset singleton for test
BackgroundJobScheduler._instance = None
s1 = BackgroundJobScheduler()
s2 = BackgroundJobScheduler()
assert s1 is s2
def test_thread_safe_singleton(self):
"""Test singleton is thread-safe."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
instances = []
def create_instance():
instances.append(BackgroundJobScheduler())
threads = [threading.Thread(target=create_instance) for _ in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
# All instances should be the same
assert all(i is instances[0] for i in instances)
class TestNewsSchedulerInit:
"""Tests for BackgroundJobScheduler initialization."""
def test_initializes_user_sessions_dict(self):
"""Test user_sessions initialized as empty dict."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
assert scheduler.user_sessions == {}
def test_initializes_lock(self):
"""Test lock is initialized."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
assert isinstance(scheduler.lock, type(threading.Lock()))
def test_initializes_background_scheduler(self):
"""Test BackgroundScheduler is initialized."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
from apscheduler.schedulers.background import BackgroundScheduler
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
assert isinstance(scheduler.scheduler, BackgroundScheduler)
def test_is_running_initially_false(self):
"""Test is_running is False initially."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
assert scheduler.is_running is False
class TestLoadDefaultConfig:
"""Tests for _load_default_config method."""
def test_returns_dict(self):
"""Test returns a dictionary."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
config = scheduler._load_default_config()
assert isinstance(config, dict)
def test_includes_enabled_key(self):
"""Test config includes enabled key."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
config = scheduler._load_default_config()
assert "enabled" in config
def test_includes_retention_hours(self):
"""Test config includes retention_hours."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
config = scheduler._load_default_config()
assert "retention_hours" in config
assert config["retention_hours"] == 48
def test_includes_max_jitter_seconds(self):
"""Test config includes max_jitter_seconds."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
config = scheduler._load_default_config()
assert "max_jitter_seconds" in config
class TestInitializeWithSettings:
"""Tests for initialize_with_settings method."""
def test_stores_settings_manager(self):
"""Test stores settings manager reference."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
mock_settings = Mock()
mock_settings.get_setting.return_value = True
scheduler.initialize_with_settings(mock_settings)
assert scheduler.settings_manager is mock_settings
def test_loads_config_from_settings(self):
"""Test loads config from settings manager."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
mock_settings = Mock()
mock_settings.get_setting.side_effect = lambda k, default: {
"news.scheduler.enabled": False,
"news.scheduler.retention_hours": 24,
}.get(k, default)
scheduler.initialize_with_settings(mock_settings)
assert scheduler.config["enabled"] is False
assert scheduler.config["retention_hours"] == 24
def test_handles_settings_exception(self):
"""Test handles exception during settings load."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
mock_settings = Mock()
mock_settings.get_setting.side_effect = Exception("Settings error")
# Should not raise
scheduler.initialize_with_settings(mock_settings)
class TestGetSetting:
"""Tests for _get_setting method."""
def test_returns_default_without_settings_manager(self):
"""Test returns default when no settings manager."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
result = scheduler._get_setting("some.key", default=42)
assert result == 42
def test_calls_settings_manager(self):
"""Test calls settings manager get_setting."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
mock_settings = Mock()
mock_settings.get_setting.return_value = 100
scheduler.settings_manager = mock_settings
_ = scheduler._get_setting("some.key", default=42)
mock_settings.get_setting.assert_called_with("some.key", default=42)
class TestDocumentSchedulerSettings:
"""Tests for DocumentSchedulerSettings dataclass."""
def test_is_frozen(self):
"""Test dataclass is frozen (immutable)."""
from local_deep_research.scheduler.background import (
DocumentSchedulerSettings,
)
settings = DocumentSchedulerSettings()
with pytest.raises(Exception): # FrozenInstanceError
settings.enabled = False
def test_default_enabled_is_true(self):
"""Test default enabled is True."""
from local_deep_research.scheduler.background import (
DocumentSchedulerSettings,
)
settings = DocumentSchedulerSettings()
assert settings.enabled is True
def test_default_interval_seconds(self):
"""Test default interval_seconds is 1800."""
from local_deep_research.scheduler.background import (
DocumentSchedulerSettings,
)
settings = DocumentSchedulerSettings()
assert settings.interval_seconds == 1800
def test_defaults_factory_method(self):
"""Test defaults() factory method."""
from local_deep_research.scheduler.background import (
DocumentSchedulerSettings,
)
defaults = DocumentSchedulerSettings.defaults()
assert defaults.enabled is True
assert defaults.interval_seconds == 1800
class TestGetDocumentSchedulerSettings:
"""Tests for _get_document_scheduler_settings method."""
def test_returns_cached_value(self):
"""Test returns cached value when available."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
DocumentSchedulerSettings,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
cached_settings = DocumentSchedulerSettings(
enabled=False, interval_seconds=600
)
scheduler._settings_cache["testuser"] = cached_settings
result = scheduler._get_document_scheduler_settings("testuser")
assert result is cached_settings
def test_returns_defaults_without_session(self):
"""Test returns defaults when no user session."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
DocumentSchedulerSettings,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
result = scheduler._get_document_scheduler_settings("unknown_user")
defaults = DocumentSchedulerSettings.defaults()
assert result.enabled == defaults.enabled
def test_force_refresh_bypasses_cache(self):
"""Test force_refresh bypasses cache."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
DocumentSchedulerSettings,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
cached_settings = DocumentSchedulerSettings(enabled=False)
scheduler._settings_cache["testuser"] = cached_settings
# Without session, should return defaults even with cache
result = scheduler._get_document_scheduler_settings(
"testuser", force_refresh=True
)
# Returns defaults since no session
assert result.enabled is True
class TestInvalidateUserSettingsCache:
"""Tests for invalidate_user_settings_cache method."""
def test_removes_user_from_cache(self):
"""Test removes user from cache."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
DocumentSchedulerSettings,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
scheduler._settings_cache["testuser"] = DocumentSchedulerSettings()
scheduler.invalidate_user_settings_cache("testuser")
assert "testuser" not in scheduler._settings_cache
def test_returns_true_if_found(self):
"""Test returns True when user was in cache."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
DocumentSchedulerSettings,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
scheduler._settings_cache["testuser"] = DocumentSchedulerSettings()
result = scheduler.invalidate_user_settings_cache("testuser")
assert result is True
def test_returns_false_if_not_found(self):
"""Test returns False when user not in cache."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
result = scheduler.invalidate_user_settings_cache("unknown_user")
assert result is False
class TestInvalidateAllSettingsCache:
"""Tests for invalidate_all_settings_cache method."""
def test_clears_all_entries(self):
"""Test clears all cache entries."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
DocumentSchedulerSettings,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
scheduler._settings_cache["user1"] = DocumentSchedulerSettings()
scheduler._settings_cache["user2"] = DocumentSchedulerSettings()
scheduler.invalidate_all_settings_cache()
assert len(scheduler._settings_cache) == 0
def test_returns_count_cleared(self):
"""Test returns count of cleared entries."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
DocumentSchedulerSettings,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
scheduler._settings_cache["user1"] = DocumentSchedulerSettings()
scheduler._settings_cache["user2"] = DocumentSchedulerSettings()
scheduler._settings_cache["user3"] = DocumentSchedulerSettings()
count = scheduler.invalidate_all_settings_cache()
assert count == 3
class TestStartScheduler:
"""Tests for start method."""
def test_sets_is_running_true(self):
"""Test sets is_running to True."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
scheduler.set_app(MagicMock())
with patch.object(scheduler.scheduler, "start"):
scheduler.start()
assert scheduler.is_running is True
def test_starts_background_scheduler(self):
"""Test starts the background scheduler."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
scheduler.set_app(MagicMock())
with patch.object(scheduler.scheduler, "start") as mock_start:
scheduler.start()
mock_start.assert_called_once()
def test_does_not_start_if_already_running(self):
"""Test does not start if already running."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
scheduler.is_running = True
with patch.object(scheduler.scheduler, "start") as mock_start:
scheduler.start()
mock_start.assert_not_called()
class TestStopScheduler:
"""Tests for stop method."""
def test_sets_is_running_false(self):
"""Test sets is_running to False."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
scheduler.is_running = True
with patch.object(scheduler.scheduler, "shutdown"):
scheduler.stop()
assert scheduler.is_running is False
def test_shuts_down_scheduler(self):
"""Test shuts down the background scheduler."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
scheduler.is_running = True
with patch.object(scheduler.scheduler, "shutdown") as mock_shutdown:
scheduler.stop()
mock_shutdown.assert_called_once()
class TestUpdateUserInfo:
"""Tests for update_user_info method."""
def test_creates_new_session(self):
"""Test creates new session for new user."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
scheduler.is_running = True # Enable scheduler
with patch.object(scheduler, "_schedule_user_subscriptions"):
scheduler.update_user_info("newuser", "password123")
assert "newuser" in scheduler.user_sessions
assert "password" not in scheduler.user_sessions["newuser"]
assert scheduler._credential_store.retrieve("newuser") == "password123"
def test_updates_last_activity(self):
"""Test updates last_activity timestamp."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
from datetime import timezone
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
scheduler.is_running = True
before = datetime.now(timezone.utc)
with patch.object(scheduler, "_schedule_user_subscriptions"):
scheduler.update_user_info("testuser", "password")
after = datetime.now(timezone.utc)
last_activity = scheduler.user_sessions["testuser"]["last_activity"]
assert before <= last_activity <= after
def test_initializes_scheduled_jobs_set(self):
"""Test initializes empty scheduled_jobs set."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
scheduler.is_running = True
with patch.object(scheduler, "_schedule_user_subscriptions"):
scheduler.update_user_info("testuser", "password")
assert scheduler.user_sessions["testuser"]["scheduled_jobs"] == set()
class TestUnregisterUser:
"""Tests for unregister_user method."""
def test_removes_user_from_sessions(self):
"""Test removes user from sessions."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
scheduler.user_sessions["testuser"] = {
"last_activity": datetime.now(),
"scheduled_jobs": set(),
}
scheduler._credential_store.store("testuser", "test")
scheduler.unregister_user("testuser")
assert "testuser" not in scheduler.user_sessions
def test_invalidates_settings_cache(self):
"""Test invalidates settings cache for user."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
DocumentSchedulerSettings,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
scheduler.user_sessions["testuser"] = {
"last_activity": datetime.now(),
"scheduled_jobs": set(),
}
scheduler._credential_store.store("testuser", "test")
scheduler._settings_cache["testuser"] = DocumentSchedulerSettings()
scheduler.unregister_user("testuser")
assert "testuser" not in scheduler._settings_cache
def test_handles_nonexistent_user(self):
"""Test handles unregistering nonexistent user."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
# Should not raise
scheduler.unregister_user("nonexistent_user")
class TestGetDocumentSchedulerStatus:
"""Tests for get_document_scheduler_status method."""
def test_returns_dict(self):
"""Test returns a dictionary."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
result = scheduler.get_document_scheduler_status("testuser")
assert isinstance(result, dict)
def test_includes_enabled_flag(self):
"""Test includes enabled flag."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
result = scheduler.get_document_scheduler_status("testuser")
assert "enabled" in result
def test_returns_disabled_for_unknown_user(self):
"""Test returns disabled for unknown user."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
result = scheduler.get_document_scheduler_status("unknown")
# Default settings should have enabled=True
assert "enabled" in result
class TestTriggerDocumentProcessing:
"""Tests for trigger_document_processing method."""
def test_returns_false_if_no_session(self):
"""Test returns False if no user session."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
result = scheduler.trigger_document_processing("unknown_user")
assert result is False
def test_returns_false_if_not_running(self):
"""Test returns False if scheduler not running."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
scheduler.is_running = False
scheduler.user_sessions["testuser"] = {
"last_activity": datetime.now(),
"scheduled_jobs": set(),
}
scheduler._credential_store.store("testuser", "test")
result = scheduler.trigger_document_processing("testuser")
assert result is False
class TestSchedulerThreadSafety:
"""Tests for scheduler thread safety."""
def test_lock_used_for_user_sessions(self):
"""Test lock is used when modifying user_sessions."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
# The lock should be accessible
assert hasattr(scheduler, "lock")
assert isinstance(scheduler.lock, type(threading.Lock()))
def test_settings_cache_lock_exists(self):
"""Test settings cache lock exists."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
assert hasattr(scheduler, "_settings_cache_lock")
assert isinstance(
scheduler._settings_cache_lock, type(threading.Lock())
)
class TestSchedulerConfigDefaults:
"""Tests for scheduler configuration defaults."""
def test_default_retention_hours_is_48(self):
"""Test default retention_hours is 48."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
assert scheduler.config["retention_hours"] == 48
def test_default_cleanup_interval_is_1_hour(self):
"""Test default cleanup_interval_hours is 1."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
assert scheduler.config["cleanup_interval_hours"] == 1
def test_default_max_jitter_is_300_seconds(self):
"""Test default max_jitter_seconds is 300."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
assert scheduler.config["max_jitter_seconds"] == 300
def test_default_max_concurrent_jobs_is_10(self):
"""Test default max_concurrent_jobs is 10."""
from local_deep_research.scheduler.background import (
BackgroundJobScheduler,
)
BackgroundJobScheduler._instance = None
scheduler = BackgroundJobScheduler()
assert scheduler.config["max_concurrent_jobs"] == 10