3131from google .adk .tools .skill_toolset import SkillToolset
3232
3333from veadk .cloud .harness_app import utils
34+ from veadk .skills .policy import parse_skill_space_policy
3435from veadk .skills .skill import Skill as VeADKSkill
3536
3637
@@ -152,6 +153,159 @@ def fail_skillhub_download(*args, **kwargs):
152153 assert isinstance (toolset ._code_executor , UnsafeLocalCodeExecutor )
153154
154155
156+ @pytest .mark .parametrize (
157+ ("policy" , "expected_names" ),
158+ [
159+ ('{"mode":"allow","ids":["s-allow"]}' , ["allowed-skill" ]),
160+ ('{"mode":"deny","ids":["s-deny"]}' , ["allowed-skill" ]),
161+ ],
162+ )
163+ def test_build_skill_toolset_applies_skill_space_policy (
164+ monkeypatch ,
165+ tmp_path ,
166+ policy ,
167+ expected_names ,
168+ ):
169+ remote_skills = [
170+ VeADKSkill (
171+ name = "allowed-skill" ,
172+ description = "Allowed skill." ,
173+ path = "skills/s-allow/v1/allowed-skill.zip" ,
174+ skill_space_id = "ss-test" ,
175+ bucket_name = "bucket" ,
176+ id = "s-allow" ,
177+ ),
178+ VeADKSkill (
179+ name = "denied-skill" ,
180+ description = "Denied skill." ,
181+ path = "skills/s-deny/v1/denied-skill.zip" ,
182+ skill_space_id = "ss-test" ,
183+ bucket_name = "bucket" ,
184+ id = "s-deny" ,
185+ ),
186+ ]
187+ materialized : list [str ] = []
188+
189+ monkeypatch .setenv ("SKILL_SPACE_POLICY" , policy )
190+ monkeypatch .setattr (
191+ utils ,
192+ "_load_skills_from_space_id" ,
193+ lambda _skill_space_id : remote_skills ,
194+ )
195+
196+ def fake_materialize_remote_skill (
197+ skill : VeADKSkill ,
198+ * ,
199+ cache_dir = None ,
200+ ):
201+ materialized .append (skill .name )
202+ skill_dir = tmp_path / skill .name
203+ _write_adk_skill (skill_dir , name = skill .name )
204+ return skill_dir
205+
206+ monkeypatch .setattr (
207+ utils ,
208+ "materialize_remote_skill" ,
209+ fake_materialize_remote_skill ,
210+ )
211+
212+ toolset = utils .build_skill_toolset (["space:ss-test" ], download_dir = tmp_path )
213+
214+ assert materialized == expected_names
215+ assert [skill .name for skill in toolset ._list_skills ()] == expected_names
216+
217+
218+ def test_build_skill_toolset_allows_empty_skill_space_policy (
219+ monkeypatch ,
220+ tmp_path ,
221+ ):
222+ remote_skills = [
223+ VeADKSkill (
224+ name = "remote-skill" ,
225+ description = "Remote skill." ,
226+ path = "skills/s-1/v1/remote-skill.zip" ,
227+ skill_space_id = "ss-test" ,
228+ bucket_name = "bucket" ,
229+ id = "s-1" ,
230+ )
231+ ]
232+
233+ monkeypatch .setenv ("SKILL_SPACE_POLICY" , '{"mode":"allow","ids":[]}' )
234+ monkeypatch .setattr (
235+ utils ,
236+ "_load_skills_from_space_id" ,
237+ lambda _skill_space_id : remote_skills ,
238+ )
239+ monkeypatch .setattr (
240+ utils ,
241+ "materialize_remote_skill" ,
242+ lambda * _args , ** _kwargs : pytest .fail (
243+ "filtered skills must not be materialized"
244+ ),
245+ )
246+
247+ assert utils .build_skill_toolset (["space:ss-test" ], download_dir = tmp_path ) is None
248+
249+
250+ def test_build_skill_toolset_prefers_explicit_policy_over_env (
251+ monkeypatch ,
252+ tmp_path ,
253+ ):
254+ remote_skills = [
255+ VeADKSkill (
256+ name = "explicit-skill" ,
257+ description = "Explicit skill." ,
258+ path = "skills/s-explicit/v1/explicit-skill.zip" ,
259+ skill_space_id = "ss-test" ,
260+ bucket_name = "bucket" ,
261+ id = "s-explicit" ,
262+ ),
263+ VeADKSkill (
264+ name = "env-skill" ,
265+ description = "Env skill." ,
266+ path = "skills/s-env/v1/env-skill.zip" ,
267+ skill_space_id = "ss-test" ,
268+ bucket_name = "bucket" ,
269+ id = "s-env" ,
270+ ),
271+ ]
272+ materialized : list [str ] = []
273+
274+ monkeypatch .setenv ("SKILL_SPACE_POLICY" , '{"mode":"allow","ids":["s-env"]}' )
275+ monkeypatch .setattr (
276+ utils ,
277+ "_load_skills_from_space_id" ,
278+ lambda _skill_space_id : remote_skills ,
279+ )
280+
281+ def fake_materialize_remote_skill (
282+ skill : VeADKSkill ,
283+ * ,
284+ cache_dir = None ,
285+ ):
286+ materialized .append (skill .name )
287+ skill_dir = tmp_path / skill .name
288+ _write_adk_skill (skill_dir , name = skill .name )
289+ return skill_dir
290+
291+ monkeypatch .setattr (
292+ utils ,
293+ "materialize_remote_skill" ,
294+ fake_materialize_remote_skill ,
295+ )
296+
297+ toolset = utils .build_skill_toolset (
298+ ["space:ss-test" ],
299+ download_dir = tmp_path ,
300+ skill_space_policy = parse_skill_space_policy (
301+ '{"mode":"allow","ids":["s-explicit"]}'
302+ ),
303+ )
304+
305+ assert materialized == ["explicit-skill" ]
306+ assert [skill .name for skill in toolset ._list_skills ()] == ["explicit-skill" ]
307+
308+
155309def test_replace_skills_replaces_existing_skill_toolset (monkeypatch , tmp_path ):
156310 existing_dir = tmp_path / "existing"
157311 incoming_dir = tmp_path / "incoming"
@@ -170,7 +324,7 @@ def test_replace_skills_replaces_existing_skill_toolset(monkeypatch, tmp_path):
170324 agent = SimpleNamespace (tools = [marker_tool , existing_toolset ])
171325 calls : list [tuple [list [str ], object ]] = []
172326
173- def fake_build_skill_toolset (skill_ids , download_dir = None ):
327+ def fake_build_skill_toolset (skill_ids , download_dir = None , skill_space_policy = None ):
174328 calls .append ((skill_ids , download_dir ))
175329 return incoming_toolset
176330
0 commit comments