diff --git a/README.md b/README.md index 817c165..76fd8e2 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ result = src.Set(config) # truthy on success; result.paths has run artifacts get_result = src.Get(config) for provider in get_result.auth_providers: ... -for code_host in get_result.code_hosts: +for code_host_connection in get_result.code_host_connections: ... # Mapping rules can be passed in memory instead of a maps YAML file - @@ -225,7 +225,7 @@ snapshots that make `--apply` reversible. ``` - Queries the Sourcegraph instance for auth providers and code host connections - - Writes generated reference files `auth-providers.yaml` and `code-hosts.yaml` under + - Writes generated reference files `auth-providers.yaml` and `code-host-connections.yaml` under `src-auth-perms-sync-runs//` - Creates an empty `maps.yaml` if it doesn't exist @@ -327,7 +327,7 @@ Run `src-auth-perms-sync --help` for options ```text src-auth-perms-sync-runs// |-- auth-providers.yaml -|-- code-hosts.yaml +|-- code-host-connections.yaml |-- maps.yaml `-- runs `-- timestamp-command @@ -341,7 +341,7 @@ src-auth-perms-sync-runs// - The `src-auth-perms-sync-runs` dir is created under your current working directory - The `` dir is created with the hostname from `SRC_ENDPOINT` - If `maps.yaml` doesn't exist already, it'll be created for you -- `auth-providers.yaml` and `code-hosts.yaml` are created / replaced by the `get` command, +- `auth-providers.yaml` and `code-host-connections.yaml` are created / replaced by the `get` command, for you to copy values from, to use in your `maps.yaml` - Only one `maps.yaml` file can be used at a time per Sourcegraph instance, as each `set --apply` command resets the state on the Sourcegraph instance to the `maps.yaml` file which was used diff --git a/dev/engineering-requests.md b/dev/engineering-requests.md index 299df62..adb3994 100644 --- a/dev/engineering-requests.md +++ b/dev/engineering-requests.md @@ -505,7 +505,7 @@ joined to `users` and `repo` for names. ### `GetPermissionSyncDiscovery` -The `get` command writes `code-hosts.yaml` and `auth-providers.yaml`, then uses +The `get` command writes `code-host-connections.yaml` and `auth-providers.yaml`, then uses the same discovery data for mapping. A single discovery endpoint would simplify this and avoid multiple round trips. diff --git a/examples/import.py b/examples/import.py index d680e37..54fc273 100644 --- a/examples/import.py +++ b/examples/import.py @@ -27,9 +27,9 @@ # Discover the instance's auth providers and code hosts discovery = src.Get(config) for auth_provider in discovery.auth_providers: - print("auth provider:", auth_provider.get("displayName")) -for code_host in discovery.code_hosts: - print("code host:", code_host.get("displayName")) + print("auth provider:", auth_provider["authProvider"].get("displayName")) +for code_host_connection in discovery.code_host_connections: + print("code host:", code_host_connection["codeHostConnection"].get("displayName")) # Configure your mapping rules mapping_rules: list[src.MappingRule] = [ diff --git a/examples/maps.yaml b/examples/maps.yaml index 92249ec..11b5338 100644 --- a/examples/maps.yaml +++ b/examples/maps.yaml @@ -1,6 +1,6 @@ # User -> Repo permission mapping rules -# Maintain your maps.yaml file, using the values from auth-providers.yaml and code-hosts.yaml, +# Maintain your maps.yaml file, using the values from auth-providers.yaml and code-host-connections.yaml, # which are created by the --get command, under `src-auth-perms-sync-runs//` # Schema details: diff --git a/src/src_auth_perms_sync/cli.py b/src/src_auth_perms_sync/cli.py index 5434608..a07edb7 100644 --- a/src/src_auth_perms_sync/cli.py +++ b/src/src_auth_perms_sync/cli.py @@ -1036,15 +1036,17 @@ def __bool__(self) -> bool: class GetResult: """Outcome of one discovery run, carrying the discovered data in memory. - `auth_providers` and `code_hosts` hold the same dicts written to - `auth-providers.yaml` and `code-hosts.yaml`, so module callers can - assemble mapping rules without re-parsing files. + `auth_providers` and `code_host_connections` hold the same dicts written to + `auth-providers.yaml` and `code-host-connections.yaml`, so module callers can + assemble mapping rules without re-parsing files. Each entry has a + copy-paste-ready selector block (`authProvider` / `codeHostConnection`) + plus an `info` block of read-only, non-matchable context. """ succeeded: bool paths: backups.RunPaths | None = None auth_providers: tuple[dict[str, Any], ...] = () - code_hosts: tuple[dict[str, Any], ...] = () + code_host_connections: tuple[dict[str, Any], ...] = () maps_created: bool = False def __bool__(self) -> bool: @@ -1060,7 +1062,7 @@ def Get(config: Config, *, event_sink: src.EventSink | None = None) -> GetResult succeeded=True, paths=run_paths, auth_providers=tuple(command_data.auth_provider_views or ()), - code_hosts=tuple(command_data.code_host_views or ()), + code_host_connections=tuple(command_data.code_host_connection_views or ()), maps_created=command_data.maps_created, ) diff --git a/src/src_auth_perms_sync/permissions/command.py b/src/src_auth_perms_sync/permissions/command.py index cc0f33e..aa93e1e 100644 --- a/src/src_auth_perms_sync/permissions/command.py +++ b/src/src_auth_perms_sync/permissions/command.py @@ -232,7 +232,7 @@ def cmd_get( ) -> run_context.CommandData: """Refresh the generated discovery YAML files. - `run_paths.code_hosts_path` receives Sourcegraph code host connection + `run_paths.code_host_connections_path` receives Sourcegraph code host connection configs, `run_paths.auth_providers_path` receives auth provider configs, and `run_paths.maps_path` names the maps file recorded in the snapshot. @@ -320,12 +320,21 @@ def cmd_get( ] if run_paths.write_files: - permissions_maps.dump_code_hosts_yaml(run_paths.code_hosts_path, services) + permissions_maps.dump_code_host_connections_yaml( + run_paths.code_host_connections_path, services + ) permissions_maps.dump_auth_providers_yaml(run_paths.auth_providers_path, providers) - log.info("Wrote %s and %s", run_paths.code_hosts_path, run_paths.auth_providers_path) + log.info( + "Wrote %s and %s", + run_paths.code_host_connections_path, + run_paths.auth_providers_path, + ) else: - log.info("Skipping code-hosts.yaml and auth-providers.yaml because --no-files is set.") - cmd_event["code_hosts_path"] = str(run_paths.code_hosts_path) + log.info( + "Skipping code-host-connections.yaml and auth-providers.yaml " + "because --no-files is set." + ) + cmd_event["code_host_connections_path"] = str(run_paths.code_host_connections_path) cmd_event["auth_providers_path"] = str(run_paths.auth_providers_path) cmd_event["maps_path"] = str(run_paths.maps_path) @@ -390,7 +399,7 @@ def cmd_get( auth_providers=raw_providers, saml_group_users=saml_group_users, auth_provider_views=providers, - code_host_views=services, + code_host_connection_views=services, ) diff --git a/src/src_auth_perms_sync/permissions/maps.py b/src/src_auth_perms_sync/permissions/maps.py index 3289e0b..7e000e4 100644 --- a/src/src_auth_perms_sync/permissions/maps.py +++ b/src/src_auth_perms_sync/permissions/maps.py @@ -46,62 +46,67 @@ def auth_provider_to_yaml( ) -> dict[str, Any]: """Render an auth provider for the YAML config. - Keys mirror the Sourcegraph site-config schema (`type`, `clientID`, - `displayName`, `configID`). `serviceID` has no direct site-config field - so we use the GraphQL name. `isBuiltin` is dropped (redundant with - `type == "builtin"`). `userCount` is our addition. + Returns a two-block entry: + + - `authProvider:` holds exactly the fields a `maps.yaml` rule can + match under `users.authProvider`, so the block is copy-paste ready. + Keys mirror the Sourcegraph site-config schema (`type`, `clientID`, + `displayName`, `configID`); `serviceID` has no direct site-config + field so we use the GraphQL name. `isBuiltin` is dropped (redundant + with `type == "builtin"`). + - `info:` holds read-only context that mapping rules cannot match on + (`userCount`, `samlGroupUserCounts`, and any non-secret + site-config extras). `site_config_entry`, when provided, is the matching `auth.providers[*]` JSONC entry (already stripped of redacted/secret fields by - `src/src_auth_perms_sync/shared/site_config.py`). Any - fields it carries that aren't already emitted from GraphQL are - surfaced verbatim, so operators see the full provider config in the - YAML - e.g. `identityProviderMetadataURL`, `serviceProviderIssuer`, - `requireEmailDomain`, `allowSignup`. Order: GraphQL-derived identity - keys first, then site-config extras, then observation-derived metadata. + `src/src_auth_perms_sync/shared/site_config.py`). Any fields it carries + that aren't already matchable go under `info:`, so operators still see + the full provider config - e.g. `identityProviderMetadataURL`, + `serviceProviderIssuer`, `requireEmailDomain`, `allowSignup`. For SAML providers, `saml_group_user_counts` (group name -> distinct - user count) is ALWAYS surfaced under `samlGroupUserCounts:`, even - when the mapping is empty. The empty case (`{}`) tells the operator - the feature is supported but the IdP didn't release any + user count) is ALWAYS surfaced under `info.samlGroupUserCounts:`, even + when the mapping is empty. Its KEYS are the valid values for an + `authProvider.samlGroup` mapping rule. The empty case (`{}`) tells the + operator the feature is supported but the IdP didn't release any `groupsAttributeName` (default `groups`) claim in this provider's assertions - typically because the IdP hasn't been configured to do - so. Operators authoring `authProvider.samlGroup` mapping rules can use this - field to size groups before writing rules, or to learn that they - need to fix their IdP first. Pass `None` (the default for non-SAML - providers) to omit the field entirely. + so. Pass `None` (the default for non-SAML providers) to omit the field. Empty-string fields are omitted - the builtin provider has no serviceID / clientID / configID, so those keys would just be noise. """ - rendered: dict[str, Any] = {"type": provider["serviceType"]} + selector: dict[str, Any] = {"type": provider["serviceType"]} if provider["serviceID"]: - rendered["serviceID"] = provider["serviceID"] + selector["serviceID"] = provider["serviceID"] if provider["clientID"]: - rendered["clientID"] = provider["clientID"] - rendered["displayName"] = provider["displayName"] + selector["clientID"] = provider["clientID"] + selector["displayName"] = provider["displayName"] if provider["configID"]: - rendered["configID"] = provider["configID"] + selector["configID"] = provider["configID"] + + info: dict[str, Any] = {} if site_config_entry is not None: - # Merge in every non-secret site-config field that isn't already - # represented by a GraphQL-derived key above. The GraphQL value - # wins on overlaps (`type`, `displayName`, `clientID`, `configID`) - # since it's the resolved view the server actually uses. + # Surface every non-secret site-config field that isn't already a + # matchable selector key. The GraphQL value wins on overlaps + # (`type`, `displayName`, `clientID`, `configID`) since it's the + # resolved view the server actually uses. for field_name, value in site_config_entry.items(): - if field_name in rendered: + if field_name in selector: continue - rendered[field_name] = value - rendered["userCount"] = user_count + info[field_name] = value + info["userCount"] = user_count if saml_group_user_counts is not None: # Sort by descending count, then group name, so the largest groups # surface first when an operator skims the file. - rendered["samlGroupUserCounts"] = dict( + info["samlGroupUserCounts"] = dict( sorted( saml_group_user_counts.items(), key=lambda item: (-item[1], item[0]), ) ) - return rendered + return {"authProvider": selector, "info": info} BUILTIN_PROVIDER_KEY: tuple[str, str, str] = ("builtin", "", "") @@ -131,26 +136,45 @@ def count_users_per_provider( def external_service_to_yaml(service: permission_types.ExternalService) -> dict[str, Any]: - """Render a code host for the YAML config. + """Render a code host connection for the YAML config. + + Returns a two-block entry: - Keys mirror the human-readable Sourcegraph GraphQL `ExternalService` - fields that maps can match. The opaque GraphQL `id` is omitted; - maps should identify code host connections with `kind`, `displayName`, - `url`, and/or `username`. + - `codeHostConnection:` holds exactly the fields a `maps.yaml` rule + can match under `repos.codeHostConnection` (`kind`, `displayName`, + `url`, and `username` when present), so the block is copy-paste + ready. The opaque GraphQL `id` is omitted. + - `info:` holds read-only context that mapping rules cannot match on + (repo count, timestamps, sync state, creator, etc.). The JSONC `config` blob is parsed only to lift its top-level - `username` into the read-only discovery YAML. The rest of `config` - is intentionally omitted because maps no longer support matching - code-host connections by arbitrary config subtrees. + `username` into the matchable block. The rest of `config` is + intentionally omitted because maps no longer support matching + code host connections by arbitrary config subtrees. - Optional / nullable fields are omitted when null/empty so the YAML + Optional / nullable info fields are omitted when null/empty so the YAML stays readable. Booleans are always emitted (true or false) so the discovered state is explicit. """ - rendered: dict[str, Any] = { + selector: dict[str, Any] = { "kind": service["kind"], "displayName": service["displayName"], "url": service["url"], + } + raw_config = service.get("config") + if raw_config: + try: + parsed_config = json5.loads(raw_config) + except ValueError: + pass + else: + if isinstance(parsed_config, dict): + config_values = cast(dict[str, Any], parsed_config) + username = config_values.get("username") + if isinstance(username, str) and username: + selector["username"] = username + + info: dict[str, Any] = { "repoCount": service["repoCount"], "createdAt": service["createdAt"], "updatedAt": service["updatedAt"], @@ -160,53 +184,48 @@ def external_service_to_yaml(service: permission_types.ExternalService) -> dict[ "supportsRepoExclusion": bool(service.get("supportsRepoExclusion")), } if service.get("lastSyncAt"): - rendered["lastSyncAt"] = service["lastSyncAt"] + info["lastSyncAt"] = service["lastSyncAt"] if service.get("nextSyncAt"): - rendered["nextSyncAt"] = service["nextSyncAt"] + info["nextSyncAt"] = service["nextSyncAt"] if service.get("lastSyncError"): - rendered["lastSyncError"] = service["lastSyncError"] + info["lastSyncError"] = service["lastSyncError"] if service.get("warning"): - rendered["warning"] = service["warning"] + info["warning"] = service["warning"] creator = service.get("creator") if creator and creator.get("username"): - rendered["creator"] = creator["username"] + info["creator"] = creator["username"] last_updater = service.get("lastUpdater") if last_updater and last_updater.get("username"): - rendered["lastUpdater"] = last_updater["username"] - raw_config = service.get("config") - if raw_config: - try: - parsed_config = json5.loads(raw_config) - except ValueError: - pass - else: - if isinstance(parsed_config, dict): - config_values = cast(dict[str, Any], parsed_config) - username = config_values.get("username") - if isinstance(username, str) and username: - rendered["username"] = username - return rendered + info["lastUpdater"] = last_updater["username"] + return {"codeHostConnection": selector, "info": info} def dump_auth_providers_yaml(path: Path, providers: list[dict[str, Any]]) -> None: header = ( "# Sourcegraph auth provider configs.\n" "# Generated/refreshed by: src-auth-perms-sync get\n" - "# Use these values when writing maps.yaml rules under `users.authProvider`.\n" + "# Each entry's `authProvider:` block is copy-paste ready: drop it under\n" + "# `users.authProvider` in a maps.yaml rule to match those users.\n" + "# `info:` is read-only context that rules cannot match on. The KEYS of\n" + "# `info.samlGroupUserCounts` are the valid `authProvider.samlGroup` values.\n" "# This file is read-only reference data; edit maps.yaml, not this file.\n" ) _dump_readonly_discovery_yaml(path, header, "authProviders", providers) -def dump_code_hosts_yaml(path: Path, code_hosts: list[dict[str, Any]]) -> None: +def dump_code_host_connections_yaml( + path: Path, code_host_connections: list[dict[str, Any]] +) -> None: header = ( "# Sourcegraph code host connection configs.\n" "# Generated/refreshed by: src-auth-perms-sync get\n" - "# Use these values when writing maps.yaml rules under `repos.codeHostConnection`.\n" - "# ExternalService.config.username is surfaced as top-level `username` when present.\n" + "# Each entry's `codeHostConnection:` block is copy-paste ready: drop it under\n" + "# `repos.codeHostConnection` in a maps.yaml rule to match those repos.\n" + "# `username` is lifted from ExternalService.config when present.\n" + "# `info:` is read-only context that rules cannot match on.\n" "# This file is read-only reference data; edit maps.yaml, not this file.\n" ) - _dump_readonly_discovery_yaml(path, header, "codeHostConnections", code_hosts) + _dump_readonly_discovery_yaml(path, header, "codeHostConnections", code_host_connections) def _dump_readonly_discovery_yaml( @@ -244,7 +263,7 @@ def create_maps_yaml_if_missing(path: Path) -> bool: content = ( "# Auth provider -> code host connection mapping rules\n" "# Maintain this file, using values from auth-providers.yaml " - "and code-hosts.yaml as references\n" + "and code-host-connections.yaml as references\n" "\n" "maps:\n" "\n" diff --git a/src/src_auth_perms_sync/shared/backups.py b/src/src_auth_perms_sync/shared/backups.py index 9c6b8f0..13dfad5 100644 --- a/src/src_auth_perms_sync/shared/backups.py +++ b/src/src_auth_perms_sync/shared/backups.py @@ -12,7 +12,7 @@ DEFAULT_MAPS_FILE_NAME = "maps.yaml" LOG_FILE_NAME = "log.json" RUNS_DIR_NAME = "runs" -CODE_HOSTS_FILE_NAME = "code-hosts.yaml" +CODE_HOST_CONNECTIONS_FILE_NAME = "code-host-connections.yaml" AUTH_PROVIDERS_FILE_NAME = "auth-providers.yaml" @@ -30,7 +30,7 @@ class RunPaths: artifacts_dir: Path endpoint_directory: Path maps_path: Path - code_hosts_path: Path + code_host_connections_path: Path auth_providers_path: Path run_directory: Path write_files: bool = True @@ -95,7 +95,7 @@ def resolve_run_paths( artifacts_dir=resolved_artifacts_dir, endpoint_directory=endpoint_directory, maps_path=resolved_maps_path, - code_hosts_path=endpoint_directory / CODE_HOSTS_FILE_NAME, + code_host_connections_path=endpoint_directory / CODE_HOST_CONNECTIONS_FILE_NAME, auth_providers_path=endpoint_directory / AUTH_PROVIDERS_FILE_NAME, run_directory=run_directory, write_files=write_files, diff --git a/src/src_auth_perms_sync/shared/run_context.py b/src/src_auth_perms_sync/shared/run_context.py index 127d16c..db619e0 100644 --- a/src/src_auth_perms_sync/shared/run_context.py +++ b/src/src_auth_perms_sync/shared/run_context.py @@ -23,8 +23,8 @@ class CommandData: """Instance data a command loaded and later commands or callers may reuse. - `auth_provider_views` and `code_host_views` carry the same dicts the get - command writes to `auth-providers.yaml` and `code-hosts.yaml`, so module + `auth_provider_views` and `code_host_connection_views` carry the same dicts the get + command writes to `auth-providers.yaml` and `code-host-connections.yaml`, so module callers receive discovery data without re-parsing files. `saml_group_users` carries the complete user population (full set @@ -39,7 +39,7 @@ class CommandData: saml_group_users: list[shared_types.SamlGroupUser] | None = None scoped_saml_group_users: list[shared_types.ScopedSamlGroupUser] | None = None auth_provider_views: list[dict[str, Any]] | None = None - code_host_views: list[dict[str, Any]] | None = None + code_host_connection_views: list[dict[str, Any]] | None = None maps_created: bool = False diff --git a/tests/unit/test_backups.py b/tests/unit/test_backups.py index 6161838..0dc4d8b 100644 --- a/tests/unit/test_backups.py +++ b/tests/unit/test_backups.py @@ -16,7 +16,7 @@ def make_run_paths(run_directory: Path) -> backups.RunPaths: artifacts_dir=run_directory.parent, endpoint_directory=run_directory.parent, maps_path=run_directory.parent / "maps.yaml", - code_hosts_path=run_directory.parent / "code-hosts.yaml", + code_host_connections_path=run_directory.parent / "code-host-connections.yaml", auth_providers_path=run_directory.parent / "auth-providers.yaml", run_directory=run_directory, ) @@ -140,8 +140,8 @@ def test_generated_yaml_paths_live_in_the_endpoint_directory(self) -> None: run_paths = self.resolve(Path(directory_name)) self.assertEqual( - run_paths.endpoint_directory / backups.CODE_HOSTS_FILE_NAME, - run_paths.code_hosts_path, + run_paths.endpoint_directory / backups.CODE_HOST_CONNECTIONS_FILE_NAME, + run_paths.code_host_connections_path, ) self.assertEqual( run_paths.endpoint_directory / backups.AUTH_PROVIDERS_FILE_NAME, diff --git a/tests/unit/test_cli_config.py b/tests/unit/test_cli_config.py index 8c1f66d..79d16b7 100644 --- a/tests/unit/test_cli_config.py +++ b/tests/unit/test_cli_config.py @@ -48,7 +48,7 @@ def make_run_paths() -> backups.RunPaths: artifacts_dir=Path("artifacts"), endpoint_directory=Path("artifacts/sourcegraph.example.com"), maps_path=Path("maps.yaml"), - code_hosts_path=Path("code-hosts.yaml"), + code_host_connections_path=Path("code-host-connections.yaml"), auth_providers_path=Path("auth-providers.yaml"), run_directory=Path("artifacts/sourcegraph.example.com/runs/run"), ) @@ -779,7 +779,9 @@ def test_cmd_get_no_backup_skips_snapshot_artifacts(self) -> None: with ( mock.patch.object(permissions_command, "load_discovery", return_value=([], [], {})), mock.patch.object(permissions_command, "load_selected_users", return_value=[]), - mock.patch.object(permissions_command.permissions_maps, "dump_code_hosts_yaml"), + mock.patch.object( + permissions_command.permissions_maps, "dump_code_host_connections_yaml" + ), mock.patch.object(permissions_command.permissions_maps, "dump_auth_providers_yaml"), mock.patch.object( permissions_command.permission_snapshot, "build_snapshot" diff --git a/tests/unit/test_command_additive.py b/tests/unit/test_command_additive.py index 838b421..736f09f 100644 --- a/tests/unit/test_command_additive.py +++ b/tests/unit/test_command_additive.py @@ -107,7 +107,7 @@ def make_run_paths(directory: Path, maps_path: Path) -> backups.RunPaths: artifacts_dir=directory / "artifacts", endpoint_directory=endpoint_directory, maps_path=maps_path, - code_hosts_path=endpoint_directory / "code-hosts.yaml", + code_host_connections_path=endpoint_directory / "code-host-connections.yaml", auth_providers_path=endpoint_directory / "auth-providers.yaml", run_directory=directory / "run-artifacts", ) @@ -158,7 +158,7 @@ def test_no_backup_dry_run_skips_artifacts_and_repo_load_when_no_rule_matches( self.assertEqual([], client.repo_service_ids) self.assertEqual(0, client.explicit_repo_fetch_count) - def test_additive_users_loads_only_referenced_code_hosts(self) -> None: + def test_additive_users_loads_only_referenced_code_host_connections(self) -> None: first_service = make_external_service(1, "GitHub Enterprise") second_service = make_external_service(2, "GitLab") client = _AdditiveCommandClient( diff --git a/tests/unit/test_command_files.py b/tests/unit/test_command_files.py index 91223bc..a7a217a 100644 --- a/tests/unit/test_command_files.py +++ b/tests/unit/test_command_files.py @@ -33,7 +33,7 @@ def make_run_paths(directory: Path, *, write_files: bool) -> backups.RunPaths: artifacts_dir=directory / "artifacts", endpoint_directory=endpoint_directory, maps_path=directory / "maps.yaml", - code_hosts_path=endpoint_directory / "code-hosts.yaml", + code_host_connections_path=endpoint_directory / "code-host-connections.yaml", auth_providers_path=endpoint_directory / "auth-providers.yaml", run_directory=endpoint_directory / "runs" / "2026-06-12-00-00-00-get", write_files=write_files, @@ -153,11 +153,14 @@ def test_no_files_run_creates_nothing_but_returns_views(self) -> None: self.assertEqual({maps_path}, set(directory.rglob("*"))) self.assertIsNotNone(command_data.auth_provider_views) - self.assertIsNotNone(command_data.code_host_views) + self.assertIsNotNone(command_data.code_host_connection_views) assert command_data.auth_provider_views is not None - assert command_data.code_host_views is not None - self.assertEqual("github", command_data.auth_provider_views[0]["type"]) - self.assertEqual("GitHub Enterprise", command_data.code_host_views[0]["displayName"]) + assert command_data.code_host_connection_views is not None + self.assertEqual("github", command_data.auth_provider_views[0]["authProvider"]["type"]) + self.assertEqual( + "GitHub Enterprise", + command_data.code_host_connection_views[0]["codeHostConnection"]["displayName"], + ) def test_writing_run_dumps_yaml_matching_the_returned_views(self) -> None: with tempfile.TemporaryDirectory() as directory_name: @@ -166,12 +169,17 @@ def test_writing_run_dumps_yaml_matching_the_returned_views(self) -> None: command_data = run_cmd_get(run_paths, do_backup=False) - self.assertTrue(run_paths.code_hosts_path.is_file()) + self.assertTrue(run_paths.code_host_connections_path.is_file()) self.assertTrue(run_paths.auth_providers_path.is_file()) - code_hosts_on_disk = yaml.safe_load(run_paths.code_hosts_path.read_text()) + code_host_connections_on_disk = yaml.safe_load( + run_paths.code_host_connections_path.read_text() + ) auth_providers_on_disk = yaml.safe_load(run_paths.auth_providers_path.read_text()) - self.assertEqual(command_data.code_host_views, code_hosts_on_disk["codeHostConnections"]) + self.assertEqual( + command_data.code_host_connection_views, + code_host_connections_on_disk["codeHostConnections"], + ) self.assertEqual(command_data.auth_provider_views, auth_providers_on_disk["authProviders"]) diff --git a/tests/unit/test_maps.py b/tests/unit/test_maps.py index a57e120..356354d 100644 --- a/tests/unit/test_maps.py +++ b/tests/unit/test_maps.py @@ -105,8 +105,9 @@ def test_external_service_to_yaml_lifts_username_without_config(self) -> None: rendered = maps.external_service_to_yaml(service) - self.assertEqual("LOB1-SA1", rendered["username"]) - self.assertNotIn("config", rendered) + self.assertEqual("LOB1-SA1", rendered["codeHostConnection"]["username"]) + self.assertNotIn("config", rendered["codeHostConnection"]) + self.assertNotIn("config", rendered["info"]) class MappingTests(unittest.TestCase): diff --git a/tests/unit/test_snapshot.py b/tests/unit/test_snapshot.py index d1ce3ae..b99bb09 100644 --- a/tests/unit/test_snapshot.py +++ b/tests/unit/test_snapshot.py @@ -453,7 +453,7 @@ def test_write_projected_snapshot_keeps_after_repos_out_of_memory(self) -> None: artifacts_dir=directory, endpoint_directory=directory, maps_path=directory / "maps.yaml", - code_hosts_path=directory / "code-hosts.yaml", + code_host_connections_path=directory / "code-host-connections.yaml", auth_providers_path=directory / "auth-providers.yaml", run_directory=directory, )