From 80c363ffc6731df320dda275e57be2ddbb7a29f9 Mon Sep 17 00:00:00 2001 From: Eric L'Heureux Date: Thu, 21 May 2026 10:15:19 -0400 Subject: [PATCH 1/4] [OJ-54951] Honor backpopulation_window_days from server endpoint Consume the per-instance `backpopulation_window_days` field surfaced by Jellyfish's `_get_git_info()` and pass it through to `JFIngestGitConfig`. Without this, GitLab agent customers were silently falling back to the jf_ingest dataclass default of 60 days, despite Jellyfish's server-side config code specifying 365 for GitLab. Falls back to leaving the kwarg unset (dataclass default) when the field is absent, for backward compatibility with older Jellyfish servers. Companion change: Jellyfish-AI/jellyfish#35100. Co-Authored-By: Claude Opus 4.7 --- jf_agent/config_file_reader.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/jf_agent/config_file_reader.py b/jf_agent/config_file_reader.py index 2aef4777..c240b587 100644 --- a/jf_agent/config_file_reader.py +++ b/jf_agent/config_file_reader.py @@ -558,6 +558,15 @@ def _make_datetimes_timezone_aware(datetime_str: str): skip_pulling_users = ADO_DEFAULT_API_URL not in base_url pull_from = _make_datetimes_timezone_aware(endpoint_git_instance_info['pull_from']) + + # Older Jellyfish servers don't send this field; fall back to the jf_ingest + # GitConfig dataclass default by leaving it unset on the kwargs below. + extra_kwargs = {} + if 'backpopulation_window_days' in endpoint_git_instance_info: + extra_kwargs['backpopulation_window_days'] = endpoint_git_instance_info[ + 'backpopulation_window_days' + ] + git_configs.append( JFIngestGitConfig( company_slug=company_slug, @@ -580,6 +589,7 @@ def _make_datetimes_timezone_aware(datetime_str: str): git_strip_text_content=agent_git_config.git_strip_text_content, check_ghc_mannequin_user_prs=agent_git_config.github_check_mannequin_users, skip_pulling_users=skip_pulling_users, + **extra_kwargs, ) ) From ae7b86dc8e483e70e917070b10306ec20427b2cb Mon Sep 17 00:00:00 2001 From: Eric L'Heureux Date: Thu, 21 May 2026 10:39:48 -0400 Subject: [PATCH 2/4] Update config_file_reader.py --- jf_agent/config_file_reader.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jf_agent/config_file_reader.py b/jf_agent/config_file_reader.py index c240b587..f5744ddc 100644 --- a/jf_agent/config_file_reader.py +++ b/jf_agent/config_file_reader.py @@ -559,8 +559,7 @@ def _make_datetimes_timezone_aware(datetime_str: str): pull_from = _make_datetimes_timezone_aware(endpoint_git_instance_info['pull_from']) - # Older Jellyfish servers don't send this field; fall back to the jf_ingest - # GitConfig dataclass default by leaving it unset on the kwargs below. + # Check if non-default backpopulation window should be used. extra_kwargs = {} if 'backpopulation_window_days' in endpoint_git_instance_info: extra_kwargs['backpopulation_window_days'] = endpoint_git_instance_info[ From 08565f56403c3a9b391ecd836a33359dfa2bf87c Mon Sep 17 00:00:00 2001 From: Eric L'Heureux Date: Thu, 21 May 2026 12:42:01 -0400 Subject: [PATCH 3/4] [OJ-54951] Add tests for backpopulation_window_days passthrough Asserts that JFIngestGitConfig receives the kwarg when the server endpoint provides it, and that the kwarg is omitted when absent so the jf_ingest default applies. Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/test_config_file.py | 124 +++++++++++++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 1 deletion(-) diff --git a/tests/test_config_file.py b/tests/test_config_file.py index ef3c2469..361bfb51 100644 --- a/tests/test_config_file.py +++ b/tests/test_config_file.py @@ -1,7 +1,7 @@ import json import unittest from unittest import TestCase -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch import yaml from jf_ingest.config import AzureDevopsAuthConfig as JFIngestAzureDevopsAuthConfig @@ -10,9 +10,52 @@ GitConfig, _get_git_config_from_yaml, _get_jf_ingest_git_auth_config, + get_ingest_config, ) +def _build_ado_git_config() -> GitConfig: + return GitConfig( + git_url='https://ado.com', + git_provider='ado', + git_instance_slug='ado-instance-1', + git_include_projects=[], + git_exclude_projects=[], + git_include_all_repos_inside_projects=[], + git_exclude_all_repos_inside_projects=[], + git_include_repos=[], + git_exclude_repos=[], + git_include_branches={}, + git_strip_text_content=False, + git_redact_names_and_urls=False, + gitlab_per_page_override=False, + git_verbose=False, + gitlab_keep_base_url=False, + creds_envvar_prefix='ORG1', + git_include_bbcloud_projects=[], + git_exclude_bbcloud_projects=[], + ) + + +def _build_ingest_config_inputs(endpoint_git_instance_info: dict): + config = MagicMock() + config.jira_url = None + config.git_configs = [_build_ado_git_config()] + config.skip_ssl_verification = False + config.run_mode_includes_send = False + config.jira_skip_saving_data_locally = False + config.outdir = '/tmp/agent-output' + config.jellyfish_api_base = 'https://api.jellyfish.co' + + creds = MagicMock() + creds.git_instance_to_creds = {'ado-instance-1': {'ado_token': 'token-1'}} + creds.jellyfish_api_token = 'jf-token' + + endpoint_git_instances_info = {'ado-instance-1': endpoint_git_instance_info} + + return config, creds, endpoint_git_instances_info + + class TestGitConfigGeneration(TestCase): def test_get_git_config_from_yaml_ado_default(self): ado_yaml_content = """ @@ -183,3 +226,82 @@ def test_get_jf_ingest_git_auth_config_convert_to_str(self): assert auth_config.token == 'token-2' assert auth_config.api_version == '7.0' assert auth_config.verify is True + + +class TestBackpopulationWindowDaysPassthrough(TestCase): + """ + Verifies the optional `backpopulation_window_days` value from the server endpoint + is forwarded into JFIngestGitConfig only when present, so that jf-ingest's default + applies otherwise. + """ + + @patch('jf_agent.config_file_reader.IngestionConfig') + @patch('jf_agent.config_file_reader.JFIngestGitConfig') + @patch('jf_agent.config_file_reader._get_jf_ingest_git_auth_config') + @patch('jf_agent.config_file_reader.get_company_info') + def test_backpopulation_window_days_forwarded_when_present( + self, + mock_get_company_info, + mock_get_auth, + mock_jf_ingest_git_config, + mock_ingestion_config, + ): + mock_get_company_info.return_value = {'company_slug': 'test-co'} + mock_get_auth.return_value = MagicMock() + + config, creds, endpoint_git_instances_info = _build_ingest_config_inputs( + endpoint_git_instance_info={ + 'slug': 'ado-instance-1', + 'key': 'ado-key', + 'repos_dict_v2': {}, + 'pull_from': '2024-01-01T00:00:00', + 'backpopulation_window_days': 90, + } + ) + + get_ingest_config( + config=config, + creds=creds, + endpoint_jira_info={}, + endpoint_git_instances_info=endpoint_git_instances_info, + jf_options={}, + ) + + self.assertEqual(mock_jf_ingest_git_config.call_count, 1) + kwargs = mock_jf_ingest_git_config.call_args.kwargs + self.assertEqual(kwargs.get('backpopulation_window_days'), 90) + + @patch('jf_agent.config_file_reader.IngestionConfig') + @patch('jf_agent.config_file_reader.JFIngestGitConfig') + @patch('jf_agent.config_file_reader._get_jf_ingest_git_auth_config') + @patch('jf_agent.config_file_reader.get_company_info') + def test_backpopulation_window_days_omitted_when_absent( + self, + mock_get_company_info, + mock_get_auth, + mock_jf_ingest_git_config, + mock_ingestion_config, + ): + mock_get_company_info.return_value = {'company_slug': 'test-co'} + mock_get_auth.return_value = MagicMock() + + config, creds, endpoint_git_instances_info = _build_ingest_config_inputs( + endpoint_git_instance_info={ + 'slug': 'ado-instance-1', + 'key': 'ado-key', + 'repos_dict_v2': {}, + 'pull_from': '2024-01-01T00:00:00', + } + ) + + get_ingest_config( + config=config, + creds=creds, + endpoint_jira_info={}, + endpoint_git_instances_info=endpoint_git_instances_info, + jf_options={}, + ) + + self.assertEqual(mock_jf_ingest_git_config.call_count, 1) + kwargs = mock_jf_ingest_git_config.call_args.kwargs + self.assertNotIn('backpopulation_window_days', kwargs) From 488e79b3dc3bd33c72a5b59478b57d51305997df Mon Sep 17 00:00:00 2001 From: Eric L'Heureux Date: Thu, 21 May 2026 12:46:05 -0400 Subject: [PATCH 4/4] [OJ-54951] Avoid hardcoded /tmp path in test mock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace `/tmp/agent-output` literal in the new tests' mock `config.outdir` with a relative string to silence the SonarCloud "publicly writable directories" finding. The path is mock-only — no file is created. Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/test_config_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_config_file.py b/tests/test_config_file.py index 361bfb51..6abd4159 100644 --- a/tests/test_config_file.py +++ b/tests/test_config_file.py @@ -44,7 +44,7 @@ def _build_ingest_config_inputs(endpoint_git_instance_info: dict): config.skip_ssl_verification = False config.run_mode_includes_send = False config.jira_skip_saving_data_locally = False - config.outdir = '/tmp/agent-output' + config.outdir = 'agent-output-test' config.jellyfish_api_base = 'https://api.jellyfish.co' creds = MagicMock()