1use crate::cache::models::{
2 CachedApertureSecret, CachedCommand, CachedParameter, CachedRequestBody, CachedSpec,
3};
4use crate::config::models::GlobalConfig;
5use crate::config::url_resolver::BaseUrlResolver;
6use crate::constants;
7use crate::error::Error;
8use crate::spec::resolve_parameter_reference;
9use crate::utils::to_kebab_case;
10use openapiv3::{OpenAPI, Operation, Parameter as OpenApiParameter, ReferenceOr, SecurityScheme};
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14#[derive(Debug, Serialize, Deserialize)]
17pub struct ApiCapabilityManifest {
18 pub api: ApiInfo,
20 pub commands: HashMap<String, Vec<CommandInfo>>,
22 pub security_schemes: HashMap<String, SecuritySchemeInfo>,
24}
25
26#[derive(Debug, Serialize, Deserialize)]
27pub struct ApiInfo {
28 pub name: String,
30 pub version: String,
32 pub description: Option<String>,
34 pub base_url: String,
36}
37
38#[derive(Debug, Serialize, Deserialize)]
39pub struct CommandInfo {
40 pub name: String,
42 pub method: String,
44 pub path: String,
46 pub description: Option<String>,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub summary: Option<String>,
51 pub operation_id: String,
53 pub parameters: Vec<ParameterInfo>,
55 pub request_body: Option<RequestBodyInfo>,
57 #[serde(skip_serializing_if = "Vec::is_empty", default)]
59 pub security_requirements: Vec<String>,
60 #[serde(skip_serializing_if = "Vec::is_empty", default)]
62 pub tags: Vec<String>,
63 #[serde(skip_serializing_if = "std::ops::Not::not", default)]
65 pub deprecated: bool,
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub external_docs_url: Option<String>,
69}
70
71#[derive(Debug, Serialize, Deserialize)]
72pub struct ParameterInfo {
73 pub name: String,
75 pub location: String,
77 pub required: bool,
79 pub param_type: String,
81 pub description: Option<String>,
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub format: Option<String>,
86 #[serde(skip_serializing_if = "Option::is_none")]
88 pub default_value: Option<String>,
89 #[serde(skip_serializing_if = "Vec::is_empty", default)]
91 pub enum_values: Vec<String>,
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub example: Option<String>,
95}
96
97#[derive(Debug, Serialize, Deserialize)]
98pub struct RequestBodyInfo {
99 pub required: bool,
101 pub content_type: String,
103 pub description: Option<String>,
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub example: Option<String>,
108}
109
110#[derive(Debug, Serialize, Deserialize)]
112pub struct SecuritySchemeInfo {
113 #[serde(rename = "type")]
115 pub scheme_type: String,
116 pub description: Option<String>,
118 #[serde(flatten)]
120 pub details: SecuritySchemeDetails,
121 #[serde(rename = "x-aperture-secret", skip_serializing_if = "Option::is_none")]
123 pub aperture_secret: Option<CachedApertureSecret>,
124}
125
126#[derive(Debug, Serialize, Deserialize)]
128#[serde(tag = "scheme", rename_all = "camelCase")]
129pub enum SecuritySchemeDetails {
130 #[serde(rename = "bearer")]
132 HttpBearer {
133 #[serde(skip_serializing_if = "Option::is_none")]
135 bearer_format: Option<String>,
136 },
137 #[serde(rename = "basic")]
139 HttpBasic,
140 #[serde(rename = "apiKey")]
142 ApiKey {
143 #[serde(rename = "in")]
145 location: String,
146 name: String,
148 },
149}
150
151pub fn generate_capability_manifest_from_openapi(
169 api_name: &str,
170 spec: &OpenAPI,
171 global_config: Option<&GlobalConfig>,
172) -> Result<String, Error> {
173 let base_url = spec.servers.first().map(|s| s.url.clone());
175 let servers: Vec<String> = spec.servers.iter().map(|s| s.url.clone()).collect();
176
177 let temp_cached_spec = CachedSpec {
178 cache_format_version: crate::cache::models::CACHE_FORMAT_VERSION,
179 name: api_name.to_string(),
180 version: spec.info.version.clone(),
181 commands: vec![], base_url,
183 servers,
184 security_schemes: HashMap::new(), skipped_endpoints: vec![], server_variables: HashMap::new(), };
188
189 let resolver = BaseUrlResolver::new(&temp_cached_spec);
191 let resolver = if let Some(config) = global_config {
192 resolver.with_global_config(config)
193 } else {
194 resolver
195 };
196 let resolved_base_url = resolver.resolve(None);
197
198 let mut command_groups: HashMap<String, Vec<CommandInfo>> = HashMap::new();
200
201 for (path, path_item) in &spec.paths.paths {
202 if let ReferenceOr::Item(item) = path_item {
203 for (method, operation) in crate::spec::http_methods_iter(item) {
205 if let Some(op) = operation {
206 let command_info = convert_openapi_operation_to_info(
207 method,
208 path,
209 op,
210 spec,
211 spec.security.as_ref(),
212 );
213
214 let group_name = op
216 .tags
217 .first()
218 .cloned()
219 .unwrap_or_else(|| constants::DEFAULT_GROUP.to_string())
220 .to_lowercase();
221
222 command_groups
223 .entry(group_name)
224 .or_default()
225 .push(command_info);
226 }
227 }
228 }
229 }
230
231 let security_schemes = extract_security_schemes_from_openapi(spec);
233
234 let manifest = ApiCapabilityManifest {
236 api: ApiInfo {
237 name: spec.info.title.clone(),
238 version: spec.info.version.clone(),
239 description: spec.info.description.clone(),
240 base_url: resolved_base_url,
241 },
242 commands: command_groups,
243 security_schemes,
244 };
245
246 serde_json::to_string_pretty(&manifest)
248 .map_err(|e| Error::serialization_error(format!("Failed to serialize agent manifest: {e}")))
249}
250
251pub fn generate_capability_manifest(
267 spec: &CachedSpec,
268 global_config: Option<&GlobalConfig>,
269) -> Result<String, Error> {
270 let mut command_groups: HashMap<String, Vec<CommandInfo>> = HashMap::new();
271
272 for cached_command in &spec.commands {
274 let group_name = if cached_command.name.is_empty() {
275 constants::DEFAULT_GROUP.to_string()
276 } else {
277 cached_command.name.to_lowercase()
278 };
279
280 let command_info = convert_cached_command_to_info(cached_command);
281 command_groups
282 .entry(group_name)
283 .or_default()
284 .push(command_info);
285 }
286
287 let resolver = BaseUrlResolver::new(spec);
289 let resolver = if let Some(config) = global_config {
290 resolver.with_global_config(config)
291 } else {
292 resolver
293 };
294 let base_url = resolver.resolve(None);
295
296 let manifest = ApiCapabilityManifest {
298 api: ApiInfo {
299 name: spec.name.clone(),
300 version: spec.version.clone(),
301 description: None, base_url,
303 },
304 commands: command_groups,
305 security_schemes: extract_security_schemes(spec),
306 };
307
308 serde_json::to_string_pretty(&manifest)
310 .map_err(|e| Error::serialization_error(format!("Failed to serialize agent manifest: {e}")))
311}
312
313fn convert_cached_command_to_info(cached_command: &CachedCommand) -> CommandInfo {
315 let command_name = if cached_command.operation_id.is_empty() {
316 cached_command.method.to_lowercase()
317 } else {
318 to_kebab_case(&cached_command.operation_id)
319 };
320
321 let parameters: Vec<ParameterInfo> = cached_command
322 .parameters
323 .iter()
324 .map(convert_cached_parameter_to_info)
325 .collect();
326
327 let request_body = cached_command
328 .request_body
329 .as_ref()
330 .map(convert_cached_request_body_to_info);
331
332 CommandInfo {
333 name: command_name,
334 method: cached_command.method.clone(),
335 path: cached_command.path.clone(),
336 description: cached_command.description.clone(),
337 summary: cached_command.summary.clone(),
338 operation_id: cached_command.operation_id.clone(),
339 parameters,
340 request_body,
341 security_requirements: cached_command.security_requirements.clone(),
342 tags: cached_command.tags.clone(),
343 deprecated: cached_command.deprecated,
344 external_docs_url: cached_command.external_docs_url.clone(),
345 }
346}
347
348fn convert_cached_parameter_to_info(cached_param: &CachedParameter) -> ParameterInfo {
350 ParameterInfo {
351 name: cached_param.name.clone(),
352 location: cached_param.location.clone(),
353 required: cached_param.required,
354 param_type: cached_param
355 .schema_type
356 .clone()
357 .unwrap_or_else(|| constants::SCHEMA_TYPE_STRING.to_string()),
358 description: cached_param.description.clone(),
359 format: cached_param.format.clone(),
360 default_value: cached_param.default_value.clone(),
361 enum_values: cached_param.enum_values.clone(),
362 example: cached_param.example.clone(),
363 }
364}
365
366fn convert_cached_request_body_to_info(cached_body: &CachedRequestBody) -> RequestBodyInfo {
368 RequestBodyInfo {
369 required: cached_body.required,
370 content_type: cached_body.content_type.clone(),
371 description: cached_body.description.clone(),
372 example: cached_body.example.clone(),
373 }
374}
375
376fn extract_security_schemes(spec: &CachedSpec) -> HashMap<String, SecuritySchemeInfo> {
378 let mut security_schemes = HashMap::new();
379
380 for (name, scheme) in &spec.security_schemes {
381 let details = match scheme.scheme_type.as_str() {
382 constants::SECURITY_TYPE_HTTP => {
383 scheme.scheme.as_ref().map_or(
384 SecuritySchemeDetails::HttpBearer {
385 bearer_format: None,
386 },
387 |http_scheme| match http_scheme.as_str() {
388 constants::AUTH_SCHEME_BEARER => SecuritySchemeDetails::HttpBearer {
389 bearer_format: scheme.bearer_format.clone(),
390 },
391 constants::AUTH_SCHEME_BASIC => SecuritySchemeDetails::HttpBasic,
392 _ => {
393 SecuritySchemeDetails::HttpBearer {
395 bearer_format: None,
396 }
397 }
398 },
399 )
400 }
401 constants::AUTH_SCHEME_APIKEY => SecuritySchemeDetails::ApiKey {
402 location: scheme
403 .location
404 .clone()
405 .unwrap_or_else(|| constants::LOCATION_HEADER.to_string()),
406 name: scheme
407 .parameter_name
408 .clone()
409 .unwrap_or_else(|| constants::HEADER_AUTHORIZATION.to_string()),
410 },
411 _ => {
412 SecuritySchemeDetails::HttpBearer {
414 bearer_format: None,
415 }
416 }
417 };
418
419 let scheme_info = SecuritySchemeInfo {
420 scheme_type: scheme.scheme_type.clone(),
421 description: scheme.description.clone(),
422 details,
423 aperture_secret: scheme.aperture_secret.clone(),
424 };
425
426 security_schemes.insert(name.clone(), scheme_info);
427 }
428
429 security_schemes
430}
431
432fn convert_openapi_operation_to_info(
434 method: &str,
435 path: &str,
436 operation: &Operation,
437 spec: &OpenAPI,
438 global_security: Option<&Vec<openapiv3::SecurityRequirement>>,
439) -> CommandInfo {
440 let command_name = operation
441 .operation_id
442 .as_ref()
443 .map_or_else(|| method.to_lowercase(), |op_id| to_kebab_case(op_id));
444
445 let parameters: Vec<ParameterInfo> = operation
447 .parameters
448 .iter()
449 .filter_map(|param_ref| match param_ref {
450 ReferenceOr::Item(param) => Some(convert_openapi_parameter_to_info(param)),
451 ReferenceOr::Reference { reference } => resolve_parameter_reference(spec, reference)
452 .ok()
453 .map(|param| convert_openapi_parameter_to_info(¶m)),
454 })
455 .collect();
456
457 let request_body = operation.request_body.as_ref().and_then(|rb_ref| {
459 if let ReferenceOr::Item(body) = rb_ref {
460 let content_type = if body.content.contains_key(constants::CONTENT_TYPE_JSON) {
462 constants::CONTENT_TYPE_JSON
463 } else {
464 body.content.keys().next().map(String::as_str)?
465 };
466
467 let media_type = body.content.get(content_type)?;
468 let example = media_type
469 .example
470 .as_ref()
471 .map(|ex| serde_json::to_string(ex).unwrap_or_else(|_| ex.to_string()));
472
473 Some(RequestBodyInfo {
474 required: body.required,
475 content_type: content_type.to_string(),
476 description: body.description.clone(),
477 example,
478 })
479 } else {
480 None
481 }
482 });
483
484 let security_requirements = operation.security.as_ref().map_or_else(
486 || {
487 global_security.map_or(vec![], |reqs| {
488 reqs.iter().flat_map(|req| req.keys().cloned()).collect()
489 })
490 },
491 |op_security| {
492 op_security
493 .iter()
494 .flat_map(|req| req.keys().cloned())
495 .collect()
496 },
497 );
498
499 CommandInfo {
500 name: command_name,
501 method: method.to_uppercase(),
502 path: path.to_string(),
503 description: operation.description.clone(),
504 summary: operation.summary.clone(),
505 operation_id: operation.operation_id.clone().unwrap_or_default(),
506 parameters,
507 request_body,
508 security_requirements,
509 tags: operation.tags.clone(),
510 deprecated: operation.deprecated,
511 external_docs_url: operation
512 .external_docs
513 .as_ref()
514 .map(|docs| docs.url.clone()),
515 }
516}
517
518fn convert_openapi_parameter_to_info(param: &OpenApiParameter) -> ParameterInfo {
520 let (param_data, location_str) = match param {
521 OpenApiParameter::Query { parameter_data, .. } => {
522 (parameter_data, constants::PARAM_LOCATION_QUERY)
523 }
524 OpenApiParameter::Header { parameter_data, .. } => {
525 (parameter_data, constants::PARAM_LOCATION_HEADER)
526 }
527 OpenApiParameter::Path { parameter_data, .. } => {
528 (parameter_data, constants::PARAM_LOCATION_PATH)
529 }
530 OpenApiParameter::Cookie { parameter_data, .. } => {
531 (parameter_data, constants::PARAM_LOCATION_COOKIE)
532 }
533 };
534
535 let (schema_type, format, default_value, enum_values, example) =
537 if let openapiv3::ParameterSchemaOrContent::Schema(schema_ref) = ¶m_data.format {
538 match schema_ref {
539 ReferenceOr::Item(schema) => {
540 let (schema_type, format, enums) = match &schema.schema_kind {
541 openapiv3::SchemaKind::Type(type_val) => match type_val {
542 openapiv3::Type::String(string_type) => {
543 let enum_values: Vec<String> = string_type
544 .enumeration
545 .iter()
546 .filter_map(|v| v.as_ref())
547 .map(|v| {
548 serde_json::to_string(v).unwrap_or_else(|_| v.to_string())
549 })
550 .collect();
551 (constants::SCHEMA_TYPE_STRING.to_string(), None, enum_values)
552 }
553 openapiv3::Type::Number(_) => {
554 (constants::SCHEMA_TYPE_NUMBER.to_string(), None, vec![])
555 }
556 openapiv3::Type::Integer(_) => {
557 (constants::SCHEMA_TYPE_INTEGER.to_string(), None, vec![])
558 }
559 openapiv3::Type::Boolean(_) => {
560 (constants::SCHEMA_TYPE_BOOLEAN.to_string(), None, vec![])
561 }
562 openapiv3::Type::Array(_) => {
563 (constants::SCHEMA_TYPE_ARRAY.to_string(), None, vec![])
564 }
565 openapiv3::Type::Object(_) => {
566 (constants::SCHEMA_TYPE_OBJECT.to_string(), None, vec![])
567 }
568 },
569 _ => (constants::SCHEMA_TYPE_STRING.to_string(), None, vec![]),
570 };
571
572 let default_value = schema
573 .schema_data
574 .default
575 .as_ref()
576 .map(|v| serde_json::to_string(v).unwrap_or_else(|_| v.to_string()));
577
578 (Some(schema_type), format, default_value, enums, None)
579 }
580 ReferenceOr::Reference { .. } => (
581 Some(constants::SCHEMA_TYPE_STRING.to_string()),
582 None,
583 None,
584 vec![],
585 None,
586 ),
587 }
588 } else {
589 (
590 Some(constants::SCHEMA_TYPE_STRING.to_string()),
591 None,
592 None,
593 vec![],
594 None,
595 )
596 };
597
598 let example = param_data
600 .example
601 .as_ref()
602 .map(|ex| serde_json::to_string(ex).unwrap_or_else(|_| ex.to_string()))
603 .or(example);
604
605 ParameterInfo {
606 name: param_data.name.clone(),
607 location: location_str.to_string(),
608 required: param_data.required,
609 param_type: schema_type.unwrap_or_else(|| constants::SCHEMA_TYPE_STRING.to_string()),
610 description: param_data.description.clone(),
611 format,
612 default_value,
613 enum_values,
614 example,
615 }
616}
617
618fn extract_security_schemes_from_openapi(spec: &OpenAPI) -> HashMap<String, SecuritySchemeInfo> {
620 let mut security_schemes = HashMap::new();
621
622 if let Some(components) = &spec.components {
623 for (name, scheme_ref) in &components.security_schemes {
624 if let ReferenceOr::Item(scheme) = scheme_ref {
625 if let Some(scheme_info) = convert_openapi_security_scheme(name, scheme) {
626 security_schemes.insert(name.clone(), scheme_info);
627 }
628 }
629 }
630 }
631
632 security_schemes
633}
634
635fn convert_openapi_security_scheme(
637 _name: &str,
638 scheme: &SecurityScheme,
639) -> Option<SecuritySchemeInfo> {
640 match scheme {
641 SecurityScheme::APIKey {
642 location,
643 name: param_name,
644 description,
645 ..
646 } => {
647 let location_str = match location {
648 openapiv3::APIKeyLocation::Query => constants::PARAM_LOCATION_QUERY,
649 openapiv3::APIKeyLocation::Header => constants::PARAM_LOCATION_HEADER,
650 openapiv3::APIKeyLocation::Cookie => constants::PARAM_LOCATION_COOKIE,
651 };
652
653 let aperture_secret = extract_aperture_secret_from_extensions(scheme);
654
655 Some(SecuritySchemeInfo {
656 scheme_type: constants::AUTH_SCHEME_APIKEY.to_string(),
657 description: description.clone(),
658 details: SecuritySchemeDetails::ApiKey {
659 location: location_str.to_string(),
660 name: param_name.clone(),
661 },
662 aperture_secret,
663 })
664 }
665 SecurityScheme::HTTP {
666 scheme: http_scheme,
667 bearer_format,
668 description,
669 ..
670 } => {
671 let details = match http_scheme.as_str() {
672 constants::AUTH_SCHEME_BEARER => SecuritySchemeDetails::HttpBearer {
673 bearer_format: bearer_format.clone(),
674 },
675 constants::AUTH_SCHEME_BASIC => SecuritySchemeDetails::HttpBasic,
676 _ => SecuritySchemeDetails::HttpBearer {
677 bearer_format: None,
678 },
679 };
680
681 let aperture_secret = extract_aperture_secret_from_extensions(scheme);
682
683 Some(SecuritySchemeInfo {
684 scheme_type: constants::SECURITY_TYPE_HTTP.to_string(),
685 description: description.clone(),
686 details,
687 aperture_secret,
688 })
689 }
690 SecurityScheme::OAuth2 { .. } | SecurityScheme::OpenIDConnect { .. } => None,
691 }
692}
693
694fn extract_aperture_secret_from_extensions(
696 scheme: &SecurityScheme,
697) -> Option<CachedApertureSecret> {
698 let extensions = match scheme {
699 SecurityScheme::APIKey { extensions, .. } | SecurityScheme::HTTP { extensions, .. } => {
700 extensions
701 }
702 SecurityScheme::OAuth2 { .. } | SecurityScheme::OpenIDConnect { .. } => return None,
703 };
704
705 extensions
706 .get(constants::EXT_APERTURE_SECRET)
707 .and_then(|value| {
708 if let Some(obj) = value.as_object() {
709 let source = obj.get(constants::EXT_KEY_SOURCE)?.as_str()?;
710 let name = obj.get(constants::EXT_KEY_NAME)?.as_str()?;
711
712 if source == constants::SOURCE_ENV {
713 return Some(CachedApertureSecret {
714 source: source.to_string(),
715 name: name.to_string(),
716 });
717 }
718 }
719 None
720 })
721}
722
723#[cfg(test)]
724mod tests {
725 use super::*;
726 use crate::cache::models::{CachedCommand, CachedParameter, CachedSpec};
727
728 #[test]
729 fn test_command_name_conversion() {
730 assert_eq!(to_kebab_case("getUserById"), "get-user-by-id");
732 assert_eq!(to_kebab_case("createUser"), "create-user");
733 assert_eq!(to_kebab_case("list"), "list");
734 assert_eq!(to_kebab_case("GET"), "get");
735 assert_eq!(
736 to_kebab_case("List an Organization's Issues"),
737 "list-an-organizations-issues"
738 );
739 }
740
741 #[test]
742 fn test_generate_capability_manifest() {
743 use crate::cache::models::{CachedApertureSecret, CachedSecurityScheme};
744
745 let mut security_schemes = HashMap::new();
746 security_schemes.insert(
747 "bearerAuth".to_string(),
748 CachedSecurityScheme {
749 name: "bearerAuth".to_string(),
750 scheme_type: constants::SECURITY_TYPE_HTTP.to_string(),
751 scheme: Some(constants::AUTH_SCHEME_BEARER.to_string()),
752 location: Some(constants::LOCATION_HEADER.to_string()),
753 parameter_name: Some(constants::HEADER_AUTHORIZATION.to_string()),
754 description: None,
755 bearer_format: None,
756 aperture_secret: Some(CachedApertureSecret {
757 source: constants::SOURCE_ENV.to_string(),
758 name: "API_TOKEN".to_string(),
759 }),
760 },
761 );
762
763 let spec = CachedSpec {
764 cache_format_version: crate::cache::models::CACHE_FORMAT_VERSION,
765 name: "Test API".to_string(),
766 version: "1.0.0".to_string(),
767 commands: vec![CachedCommand {
768 name: "users".to_string(),
769 description: Some("Get user by ID".to_string()),
770 summary: None,
771 operation_id: "getUserById".to_string(),
772 method: constants::HTTP_METHOD_GET.to_string(),
773 path: "/users/{id}".to_string(),
774 parameters: vec![CachedParameter {
775 name: "id".to_string(),
776 location: constants::PARAM_LOCATION_PATH.to_string(),
777 required: true,
778 description: None,
779 schema: Some(constants::SCHEMA_TYPE_STRING.to_string()),
780 schema_type: Some(constants::SCHEMA_TYPE_STRING.to_string()),
781 format: None,
782 default_value: None,
783 enum_values: vec![],
784 example: None,
785 }],
786 request_body: None,
787 responses: vec![],
788 security_requirements: vec!["bearerAuth".to_string()],
789 tags: vec!["users".to_string()],
790 deprecated: false,
791 external_docs_url: None,
792 }],
793 base_url: Some("https://test-api.example.com".to_string()),
794 servers: vec!["https://test-api.example.com".to_string()],
795 security_schemes,
796 skipped_endpoints: vec![],
797 server_variables: HashMap::new(),
798 };
799
800 let manifest_json = generate_capability_manifest(&spec, None).unwrap();
801 let manifest: ApiCapabilityManifest = serde_json::from_str(&manifest_json).unwrap();
802
803 assert_eq!(manifest.api.name, "Test API");
804 assert_eq!(manifest.api.version, "1.0.0");
805 assert!(manifest.commands.contains_key("users"));
806
807 let users_commands = &manifest.commands["users"];
808 assert_eq!(users_commands.len(), 1);
809 assert_eq!(users_commands[0].name, "get-user-by-id");
810 assert_eq!(users_commands[0].method, constants::HTTP_METHOD_GET);
811 assert_eq!(users_commands[0].parameters.len(), 1);
812 assert_eq!(users_commands[0].parameters[0].name, "id");
813
814 assert!(!manifest.security_schemes.is_empty());
816 assert!(manifest.security_schemes.contains_key("bearerAuth"));
817 let bearer_auth = &manifest.security_schemes["bearerAuth"];
818 assert_eq!(bearer_auth.scheme_type, constants::SECURITY_TYPE_HTTP);
819 assert!(matches!(
820 &bearer_auth.details,
821 SecuritySchemeDetails::HttpBearer { .. }
822 ));
823 assert!(bearer_auth.aperture_secret.is_some());
824 let aperture_secret = bearer_auth.aperture_secret.as_ref().unwrap();
825 assert_eq!(aperture_secret.name, "API_TOKEN");
826 assert_eq!(aperture_secret.source, constants::SOURCE_ENV);
827 }
828}