1#[cfg(test)]
6pub(crate) mod for_tests {
7 use crate::cache::for_tests::CacheForTest;
8 use anyhow::{Error, anyhow};
9 use blobfs_ramdisk::BlobfsRamdisk;
10 use fidl_fuchsia_io as fio;
11 use fidl_fuchsia_metrics as fmetrics;
12 use fidl_fuchsia_pkg as fpkg;
13 use fidl_fuchsia_pkg_ext::RepositoryConfigs;
14 use fuchsia_async as fasync;
15 use fuchsia_component_test::{
16 Capability, ChildOptions, ChildRef, DirectoryContents, RealmBuilder, RealmInstance, Ref,
17 Route,
18 };
19 use fuchsia_pkg_testing::serve::ServedRepository;
20 use fuchsia_url::RepositoryUrl;
21 use futures::{FutureExt as _, StreamExt as _};
22 use std::sync::Arc;
23
24 const SSL_TEST_CERTS_PATH: &str = "/pkg/data/ssl/cert.pem";
25 const SSL_CERT_FILE_NAME: &str = "cert.pem";
26 pub const EMPTY_REPO_PATH: &str = "/pkg/empty-repo";
27
28 pub struct Resolver {
30 proxy: fpkg::PackageResolverProxy,
31 }
32
33 impl Resolver {
34 fn new_with_proxy(pkg_resolver_proxy: fpkg::PackageResolverProxy) -> Result<Self, Error> {
35 Ok(Self { proxy: pkg_resolver_proxy })
36 }
37 }
38
39 pub struct ResolverForTest {
41 pub cache: CacheForTest,
42 pub resolver: Arc<Resolver>,
43 _served_repo: Arc<ServedRepository>,
44 }
45
46 pub struct ResolverRealm {
47 pub cache: ChildRef,
48 }
49
50 impl ResolverForTest {
51 pub async fn realm_setup(
52 realm_builder: &RealmBuilder,
53 served_repo: Arc<ServedRepository>,
54 repo_url: RepositoryUrl,
55 blobfs: &BlobfsRamdisk,
56 ) -> Result<ResolverRealm, Error> {
57 let cache_ref =
58 CacheForTest::realm_setup(realm_builder, blobfs).await.expect("setting up cache");
59
60 let repo_config =
61 RepositoryConfigs::Version1(vec![served_repo.make_repo_config(repo_url)]);
62
63 let cert_bytes = std::fs::read(std::path::Path::new(SSL_TEST_CERTS_PATH)).unwrap();
64
65 let service_reflector = realm_builder
66 .add_local_child(
67 "pkg_resolver_service_reflector",
68 move |handles| {
69 let mut fs = fuchsia_component::server::ServiceFs::new();
70 fs.dir("svc").add_fidl_service(move |stream| {
72 fasync::Task::spawn(
73 Arc::new(mock_metrics::MockMetricEventLoggerFactory::new())
74 .run_logger_factory(stream),
75 )
76 .detach()
77 });
78 async move {
79 fs.serve_connection(handles.outgoing_dir).unwrap();
80 let () = fs.collect().await;
81 Ok(())
82 }
83 .boxed()
84 },
85 ChildOptions::new(),
86 )
87 .await
88 .unwrap();
89
90 let http_client = realm_builder
91 .add_child("http-client", "#meta/http-client.cm", ChildOptions::new())
92 .await
93 .unwrap();
94 realm_builder
95 .add_route(
96 Route::new()
97 .capability(Capability::configuration(
98 "fuchsia.http-client.StopOnIdleTimeoutMillis",
99 ))
100 .capability(Capability::configuration(
101 "fuchsia.http-client.TcpReceiveBufferSizeBytes",
102 ))
103 .from(Ref::void())
104 .to(&http_client),
105 )
106 .await
107 .unwrap();
108
109 let pkg_resolver = realm_builder
110 .add_child("pkg-resolver", "#meta/pkg-resolver.cm", ChildOptions::new())
111 .await
112 .unwrap();
113
114 realm_builder
115 .read_only_directory(
116 "config-data",
117 vec![&pkg_resolver],
118 DirectoryContents::new().add_file(
119 "repositories/test.json",
120 serde_json::to_string(&repo_config).unwrap(),
121 ),
122 )
123 .await
124 .unwrap();
125
126 realm_builder
127 .read_only_directory(
128 "root-ssl-certificates",
129 vec![&http_client],
130 DirectoryContents::new().add_file(SSL_CERT_FILE_NAME, cert_bytes),
131 )
132 .await
133 .unwrap();
134
135 realm_builder
136 .add_route(
137 Route::new()
138 .capability(Capability::protocol_by_name("fuchsia.net.name.Lookup"))
139 .capability(Capability::protocol_by_name("fuchsia.posix.socket.Provider"))
140 .from(Ref::parent())
141 .to(&http_client),
142 )
143 .await
144 .unwrap();
145
146 realm_builder
147 .add_route(
148 Route::new()
149 .capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
150 .from(Ref::parent())
151 .to(&pkg_resolver)
152 .to(&http_client),
153 )
154 .await
155 .unwrap();
156
157 realm_builder
158 .add_route(
159 Route::new()
160 .capability(Capability::protocol_by_name("fuchsia.pkg.http.Client"))
161 .capability(Capability::protocol_by_name("fuchsia.net.http.Loader"))
162 .from(&http_client)
163 .to(&pkg_resolver),
164 )
165 .await
166 .unwrap();
167
168 realm_builder
169 .add_route(
170 Route::new()
171 .capability(Capability::protocol_by_name("fuchsia.pkg.PackageCache"))
172 .capability(Capability::protocol_by_name(
173 "fuchsia.pkg.garbagecollector.Manager",
174 ))
175 .from(&cache_ref)
176 .to(&pkg_resolver),
177 )
178 .await
179 .unwrap();
180
181 realm_builder
182 .add_route(
183 Route::new()
184 .capability(Capability::protocol_by_name("fuchsia.pkg.http.Client"))
185 .from(&http_client)
186 .to(&cache_ref),
187 )
188 .await
189 .unwrap();
190
191 realm_builder
192 .add_route(
193 Route::new()
194 .capability(Capability::protocol_by_name("fuchsia.pkg.Authority").weak())
195 .from(&pkg_resolver)
196 .to(&cache_ref),
197 )
198 .await
199 .unwrap();
200
201 realm_builder
202 .add_route(
203 Route::new()
204 .capability(
205 Capability::protocol::<fmetrics::MetricEventLoggerFactoryMarker>(),
206 )
207 .from(&service_reflector)
208 .to(&pkg_resolver),
209 )
210 .await
211 .unwrap();
212
213 realm_builder
214 .add_route(
215 Route::new()
216 .capability(Capability::configuration(
217 "fuchsia.pkgresolver.BlobDownloadConcurrencyLimit",
218 ))
219 .capability(Capability::configuration(
220 "fuchsia.pkgresolver.BlobDownloadResumptionAttemptsLimit",
221 ))
222 .capability(Capability::configuration(
223 "fuchsia.pkgresolver.BlobNetworkBodyTimeoutSeconds",
224 ))
225 .capability(Capability::configuration(
226 "fuchsia.pkgresolver.BlobNetworkHeaderTimeoutSeconds",
227 ))
228 .capability(Capability::configuration(
229 "fuchsia.pkgresolver.DeliveryBlobType",
230 ))
231 .capability(Capability::configuration(
232 "fuchsia.pkgresolver.TufMetadataTimeoutSeconds",
233 ))
234 .capability(Capability::configuration(
235 "fuchsia.pkgresolver.TufNetworkHeaderTimeoutSeconds",
236 ))
237 .from(Ref::void())
238 .to(&pkg_resolver),
239 )
240 .await
241 .unwrap();
242
243 Ok(ResolverRealm { cache: cache_ref })
244 }
245
246 pub async fn new(
247 realm_instance: &RealmInstance,
248 blobfs: BlobfsRamdisk,
249 served_repo: Arc<ServedRepository>,
250 ) -> Result<Self, Error> {
251 let cache = CacheForTest::new(realm_instance, blobfs).await.unwrap();
252
253 let resolver_proxy = realm_instance
254 .root
255 .connect_to_named_protocol_at_exposed_dir::<fpkg::PackageResolverMarker>(
256 "fuchsia.pkg.PackageResolver-ota",
257 )
258 .expect("connect to ota resolver");
259
260 let resolver = Resolver::new_with_proxy(resolver_proxy).unwrap();
261
262 let cache_proxy = cache.cache.package_cache_proxy().unwrap();
263
264 assert_eq!(cache_proxy.sync().await.unwrap(), Ok(()));
265
266 Ok(ResolverForTest { cache, resolver: Arc::new(resolver), _served_repo: served_repo })
267 }
268
269 pub async fn resolve_package(
272 &self,
273 url: &str,
274 ) -> Result<(fio::DirectoryProxy, fpkg::ResolutionContext), Error> {
275 let proxy = &self.resolver.proxy;
276 let (package, package_remote) = fidl::endpoints::create_proxy();
277 let resolved_context = proxy
278 .resolve(url, package_remote)
279 .await
280 .unwrap()
281 .map_err(|e| anyhow!("Package resolver error: {:?}", e))?;
282 Ok((package, resolved_context))
283 }
284 }
285}
286
287#[cfg(test)]
288pub mod tests {
289 use super::for_tests::{EMPTY_REPO_PATH, ResolverForTest};
290 use anyhow::{Context, Error};
291 use fuchsia_component_test::RealmBuilder;
292 use fuchsia_pkg_testing::{PackageBuilder, RepositoryBuilder};
293 use std::sync::Arc;
294
295 const TEST_REPO_URL: &str = "fuchsia-pkg://test";
296
297 #[fuchsia::test]
298 pub async fn test_resolver() -> Result<(), Error> {
299 let name = "test-resolver";
300 let package = PackageBuilder::new(name)
301 .add_resource_at("data/file1", "hello".as_bytes())
302 .add_resource_at("data/file2", "hello two".as_bytes())
303 .build()
304 .await
305 .unwrap();
306 let repo = Arc::new(
307 RepositoryBuilder::from_template_dir(EMPTY_REPO_PATH)
308 .add_package(&package)
309 .build()
310 .await
311 .context("Building repo")
312 .unwrap(),
313 );
314
315 let realm_builder = RealmBuilder::new().await.unwrap();
316 let served_repo = Arc::new(Arc::clone(&repo).server().start().unwrap());
317 let blobfs =
318 blobfs_ramdisk::BlobfsRamdisk::start().await.context("starting blobfs").unwrap();
319 let _resolver_realm = ResolverForTest::realm_setup(
320 &realm_builder,
321 Arc::clone(&served_repo),
322 TEST_REPO_URL.parse().unwrap(),
323 &blobfs,
324 )
325 .await
326 .unwrap();
327
328 let realm_instance = realm_builder.build().await.unwrap();
329
330 let resolver = ResolverForTest::new(&realm_instance, blobfs, served_repo)
331 .await
332 .context("launching resolver")?;
333 let (root_dir, _resolved_context) =
334 resolver.resolve_package(&format!("{TEST_REPO_URL}/{name}")).await.unwrap();
335
336 package.verify_contents(&root_dir).await.unwrap();
337 Ok(())
338 }
339}