diff --git a/changedetectionio/api/Tags.py b/changedetectionio/api/Tags.py index f09a5ef8a..33791e346 100644 --- a/changedetectionio/api/Tags.py +++ b/changedetectionio/api/Tags.py @@ -67,7 +67,25 @@ class Tag(Resource): tag.commit() return "OK", 200 - return tag + # Filter out Watch-specific runtime fields that don't apply to Tags (yet) + # TODO: Future enhancement - aggregate these values from all Watches that have this tag: + # - check_count: sum of all watches' check_count + # - last_checked: most recent last_checked from all watches + # - last_changed: most recent last_changed from all watches + # - consecutive_filter_failures: count of watches with failures + # - etc. + # These come from watch_base inheritance but currently have no meaningful value for Tags + watch_only_fields = { + 'browser_steps_last_error_step', 'check_count', 'consecutive_filter_failures', + 'content-type', 'fetch_time', 'last_changed', 'last_checked', 'last_error', + 'last_notification_error', 'last_viewed', 'notification_alert_count', + 'page_title', 'previous_md5', 'previous_md5_before_filters', 'remote_server_reply' + } + + # Create clean tag dict without Watch-specific fields + clean_tag = {k: v for k, v in tag.items() if k not in watch_only_fields} + + return clean_tag @auth.check_token @validate_openapi_request('deleteTag') diff --git a/changedetectionio/tests/test_api_tags.py b/changedetectionio/tests/test_api_tags.py index 8622fcf80..bf2823efd 100644 --- a/changedetectionio/tests/test_api_tags.py +++ b/changedetectionio/tests/test_api_tags.py @@ -207,7 +207,8 @@ def test_roundtrip_API(client, live_server, measure_memory_usage, datastore_path tag = res.json - tag['last_changed'] = 454444444444 + # Only test with date_created (readOnly field that should be filtered out) + # last_changed is Watch-specific and doesn't apply to Tags tag['date_created'] = 454444444444 # HTTP PUT ( UPDATE an existing watch ) @@ -221,14 +222,11 @@ def test_roundtrip_API(client, live_server, measure_memory_usage, datastore_path print(f"Error: {res.data}") assert res.status_code == 200, "HTTP PUT update was sent OK" + # Verify readOnly fields like date_created cannot be overridden res = client.get( - url_for("watch", uuid=uuid), + url_for("tag", uuid=uuid), headers={'x-api-key': api_key} ) - last_changed = res.json.get('last_changed') - assert last_changed != 454444444444 - assert last_changed != "454444444444" - date_created = res.json.get('date_created') - assert date_created != 454444444444 - assert date_created != "454444444444" + assert date_created != 454444444444, "ReadOnly date_created should not be updateable" + assert date_created != "454444444444", "ReadOnly date_created should not be updateable" diff --git a/docs/api-spec.yaml b/docs/api-spec.yaml index 25334be4f..daac3b7e7 100644 --- a/docs/api-spec.yaml +++ b/docs/api-spec.yaml @@ -566,8 +566,28 @@ components: - type: object properties: overrides_watch: - type: boolean - description: If true, this tag's settings override watch settings for all watches in this tag/group + type: [boolean, 'null'] + description: | + Whether this tag's settings override watch settings for all watches in this tag/group. + - true: Tag settings override watch settings + - false: Tag settings do not override (watches use their own settings) + - null: Not decided yet / inherit default behavior + # Future: Aggregated statistics from all watches with this tag + # check_count: + # type: integer + # description: Sum of check_count from all watches with this tag + # readOnly: true + # x-computed: true + # last_checked: + # type: integer + # description: Most recent last_checked timestamp from all watches with this tag + # readOnly: true + # x-computed: true + # last_changed: + # type: integer + # description: Most recent last_changed timestamp from all watches with this tag + # readOnly: true + # x-computed: true CreateTag: allOf: