···135135 end
136136 end
137137138138+ describe "build_profile_targets/1" do
139139+ test "generates nixos target for nixos subscription" do
140140+ subscriptions = [
141141+ %{seed_type: "nixos", seed_name: "myhost", rules: []}
142142+ ]
143143+144144+ targets = Profile.build_profile_targets(subscriptions)
145145+146146+ assert targets == [%{type: "nixos", path: "/nix/var/nix/profiles/system"}]
147147+ end
148148+149149+ test "generates home-manager target with current user when no username rule" do
150150+ subscriptions = [
151151+ %{seed_type: "home-manager", seed_name: "user@host", rules: []}
152152+ ]
153153+154154+ targets = Profile.build_profile_targets(subscriptions)
155155+ [target] = targets
156156+157157+ assert target.type == "home-manager"
158158+ # Path should use XDG_STATE_HOME or default
159159+ assert target.path =~ "home-manager"
160160+ end
161161+162162+ test "generates home-manager target with username from rules" do
163163+ subscriptions = [
164164+ %{
165165+ seed_type: "home-manager",
166166+ seed_name: "alice@host",
167167+ rules: [%{key: "username", op: "eq", value: "alice"}]
168168+ }
169169+ ]
170170+171171+ targets = Profile.build_profile_targets(subscriptions)
172172+173173+ assert targets == [
174174+ %{type: "home-manager", path: "/home/alice/.local/state/nix/profiles/home-manager"}
175175+ ]
176176+ end
177177+178178+ test "generates unique targets for multiple nixos subscriptions" do
179179+ subscriptions = [
180180+ %{seed_type: "nixos", seed_name: "host1", rules: []},
181181+ %{seed_type: "nixos", seed_name: "host2", rules: []}
182182+ ]
183183+184184+ targets = Profile.build_profile_targets(subscriptions)
185185+186186+ # Should deduplicate - only one system profile
187187+ assert length(targets) == 1
188188+ assert hd(targets) == %{type: "nixos", path: "/nix/var/nix/profiles/system"}
189189+ end
190190+191191+ test "generates targets for multiple home-manager users" do
192192+ subscriptions = [
193193+ %{
194194+ seed_type: "home-manager",
195195+ seed_name: "alice@host",
196196+ rules: [%{key: "username", value: "alice"}]
197197+ },
198198+ %{
199199+ seed_type: "home-manager",
200200+ seed_name: "bob@host",
201201+ rules: [%{key: "username", value: "bob"}]
202202+ }
203203+ ]
204204+205205+ targets = Profile.build_profile_targets(subscriptions)
206206+207207+ assert length(targets) == 2
208208+209209+ assert %{type: "home-manager", path: "/home/alice/.local/state/nix/profiles/home-manager"} in targets
210210+211211+ assert %{type: "home-manager", path: "/home/bob/.local/state/nix/profiles/home-manager"} in targets
212212+ end
213213+214214+ test "generates targets for mixed nixos and home-manager subscriptions" do
215215+ subscriptions = [
216216+ %{seed_type: "nixos", seed_name: "myhost", rules: []},
217217+ %{
218218+ seed_type: "home-manager",
219219+ seed_name: "alice@host",
220220+ rules: [%{key: "username", value: "alice"}]
221221+ }
222222+ ]
223223+224224+ targets = Profile.build_profile_targets(subscriptions)
225225+226226+ assert length(targets) == 2
227227+ assert %{type: "nixos", path: "/nix/var/nix/profiles/system"} in targets
228228+229229+ assert %{type: "home-manager", path: "/home/alice/.local/state/nix/profiles/home-manager"} in targets
230230+ end
231231+232232+ test "returns empty list for empty subscriptions" do
233233+ assert Profile.build_profile_targets([]) == []
234234+ end
235235+236236+ test "returns empty list for subscriptions with unsupported seed types" do
237237+ subscriptions = [
238238+ %{seed_type: "unknown-type", seed_name: "test", rules: []}
239239+ ]
240240+241241+ assert Profile.build_profile_targets(subscriptions) == []
242242+ end
243243+244244+ test "handles subscriptions with nil rules" do
245245+ subscriptions = [
246246+ %{seed_type: "nixos", seed_name: "myhost", rules: nil}
247247+ ]
248248+249249+ targets = Profile.build_profile_targets(subscriptions)
250250+ assert targets == [%{type: "nixos", path: "/nix/var/nix/profiles/system"}]
251251+ end
252252+ end
253253+254254+ describe "home_manager_profile_path/1" do
255255+ test "uses getent to get home directory" do
256256+ # This test verifies the function calls get_user_home
257257+ # Actual getent behavior is tested in get_user_home/1 tests
258258+ result = Profile.home_manager_profile_path("alice")
259259+260260+ # Should return {:ok, path} or {:error, reason}
261261+ assert match?({:ok, _path}, result) or match?({:error, _reason}, result)
262262+ end
263263+ end
264264+265265+ describe "get_user_home/1" do
266266+ test "parses getent output successfully" do
267267+ # Mock getent by temporarily redefining System.cmd
268268+ # Note: In actual test environment, getent may not be available
269269+ # so we test the parsing logic
270270+ _output = "alice:x:1000:1000:Alice User:/home/alice:/bin/bash"
271271+272272+ # Test the parsing directly via get_user_home
273273+ # If getent succeeds, it should return the home directory
274274+ case Profile.get_user_home("root") do
275275+ {:ok, home} ->
276276+ # getent worked - verify we got a path
277277+ assert is_binary(home)
278278+ assert home != ""
279279+280280+ {:error, _} ->
281281+ # getent not available - fallback should work
282282+ # This is acceptable in test environments
283283+ :ok
284284+ end
285285+ end
286286+287287+ test "returns error for non-existent user" do
288288+ result = Profile.get_user_home("nonexistentuser12345")
289289+290290+ # Should return error since this user doesn't exist
291291+ assert match?({:error, _}, result) or match?({:ok, "/home/nonexistentuser12345"}, result)
292292+ # The last resort fallback will return /home/<username>
293293+ end
294294+ end
295295+296296+ describe "home_manager_profile_path/0" do
297297+ test "uses XDG_STATE_HOME when available" do
298298+ # Store original value
299299+ original_xdg = System.get_env("XDG_STATE_HOME")
300300+ original_user = System.get_env("USER")
301301+302302+ try do
303303+ System.put_env("XDG_STATE_HOME", "/custom/state")
304304+ System.put_env("USER", "testuser")
305305+306306+ assert {:ok, "/custom/state/nix/profiles/home-manager"} =
307307+ Profile.home_manager_profile_path()
308308+ after
309309+ # Restore original values
310310+ if original_xdg,
311311+ do: System.put_env("XDG_STATE_HOME", original_xdg),
312312+ else: System.delete_env("XDG_STATE_HOME")
313313+314314+ if original_user,
315315+ do: System.put_env("USER", original_user),
316316+ else: System.delete_env("USER")
317317+ end
318318+ end
319319+320320+ test "falls back to USER when XDG_STATE_HOME not set" do
321321+ original_xdg = System.get_env("XDG_STATE_HOME")
322322+ original_user = System.get_env("USER")
323323+324324+ try do
325325+ System.delete_env("XDG_STATE_HOME")
326326+ System.put_env("USER", "root")
327327+328328+ result = Profile.home_manager_profile_path()
329329+330330+ # Should return {:ok, path} or {:error, reason} tuple
331331+ case result do
332332+ {:ok, path} when is_binary(path) ->
333333+ # Success - should contain the profile path
334334+ assert path =~ "home-manager"
335335+336336+ {:error, _reason} ->
337337+ # Error is acceptable in test environment
338338+ :ok
339339+ end
340340+ after
341341+ if original_xdg,
342342+ do: System.put_env("XDG_STATE_HOME", original_xdg),
343343+ else: System.delete_env("XDG_STATE_HOME")
344344+345345+ if original_user,
346346+ do: System.put_env("USER", original_user),
347347+ else: System.delete_env("USER")
348348+ end
349349+ end
350350+ end
351351+138352 describe "extract_generation_number/1" do
139353 test "extracts generation number from standard link path" do
140354 assert Profile.extract_generation_number("/nix/var/nix/profiles/system-42-link") == 42
···156370 test "returns nil for non-matching patterns" do
157371 assert Profile.extract_generation_number("/nix/store/abc123-package") == nil
158372 assert Profile.extract_generation_number("system-42") == nil
373373+ end
374374+ end
375375+376376+ describe "collect_profiles_for_subscriptions/1" do
377377+ test "returns empty report for empty subscriptions" do
378378+ report = Profile.collect_profiles_for_subscriptions([])
379379+ assert report.profiles == []
380380+ end
381381+382382+ test "collects profiles when subscriptions exist and paths exist" do
383383+ # This test runs on a system that may have /nix/var/nix/profiles/system
384384+ # so we just verify it returns a report (empty or not)
385385+ subscriptions = [
386386+ %{seed_type: "nixos", seed_name: "test", rules: []}
387387+ ]
388388+389389+ report = Profile.collect_profiles_for_subscriptions(subscriptions)
390390+ # Report structure should be valid regardless of whether profiles exist
391391+ assert is_list(report.profiles)
159392 end
160393 end
161394