Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/workflows/login.zig
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub fn handleLogin(allocator: std.mem.Allocator, codex_home: []const u8, opts: c
const login_codex_home = try loginScratchCodexHomeAlloc(allocator, codex_home);
defer allocator.free(login_codex_home);
defer std.Io.Dir.cwd().deleteTree(app_runtime.io(), login_codex_home) catch {};
try registry.ensurePrivateDir(login_codex_home);

try cli.login.runCodexLogin(opts, login_codex_home);
const login_auth_path = try registry.activeAuthPath(allocator, login_codex_home);
Expand Down
84 changes: 84 additions & 0 deletions tests/cli_integration_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,35 @@ fn writeSuccessfulFakeCodex(dir: fs.Dir) !void {
}
}

fn writeStrictExistingCodexHomeFakeCodex(dir: fs.Dir) !void {
const script =
if (builtin.os.tag == .windows)
"@echo off\r\n" ++
">\"%HOME%\\fake-codex-argv.txt\" echo %*\r\n" ++
">\"%HOME%\\fake-codex-home.txt\" echo %CODEX_HOME%\r\n" ++
"set \"CODEX_HOME_DIR=%CODEX_HOME%\"\r\n" ++
"if \"%CODEX_HOME_DIR%\"==\"\" set \"CODEX_HOME_DIR=%HOME%\\.codex\"\r\n" ++
"if not exist \"%CODEX_HOME_DIR%\" exit /b 42\r\n" ++
"copy /Y \"%HOME%\\fake-auth.json\" \"%CODEX_HOME_DIR%\\auth.json\" >NUL\r\n" ++
"exit /b 0\r\n"
else
"#!/bin/sh\n" ++
"printf '%s\\n' \"$*\" > \"$HOME/fake-codex-argv.txt\"\n" ++
"printf '%s\\n' \"$CODEX_HOME\" > \"$HOME/fake-codex-home.txt\"\n" ++
"CODEX_HOME_DIR=\"${CODEX_HOME:-$HOME/.codex}\"\n" ++
"[ -d \"$CODEX_HOME_DIR\" ] || exit 42\n" ++
"cp \"$HOME/fake-auth.json\" \"$CODEX_HOME_DIR/auth.json\"\n" ++
"exit 0\n";
const sub_path = fakeCodexCommandPath();
try dir.writeFile(.{ .sub_path = sub_path, .data = script });

if (builtin.os.tag != .windows) {
var file = try dir.openFile(sub_path, .{ .mode = .read_write });
defer file.close();
try file.chmod(0o755);
}
}

fn fakeNodeCommandPath() []const u8 {
return if (builtin.os.tag == .windows) "fake-node-bin/node.cmd" else "fake-node-bin/node";
}
Expand Down Expand Up @@ -791,6 +820,61 @@ test "Scenario: Given device auth login when running login then it forwards the
try std.testing.expectEqualStrings(fake_auth, active_auth);
}

test "Scenario: Given strict codex login when running login then scratch CODEX_HOME exists before launch" {
const gpa = std.testing.allocator;
const project_root = try projectRootAlloc(gpa);
defer gpa.free(project_root);
try buildCliBinary(gpa, project_root);

var tmp = fs.tmpDir(.{});
defer tmp.cleanup();

const home_root = try tmp.dir.realpathAlloc(gpa, ".");
defer gpa.free(home_root);
try tmp.dir.makePath(".codex");
try tmp.dir.makePath("fake-bin");

const expected_email = "strict-login@example.com";
const fake_auth = try fixtures.authJsonWithEmailPlan(gpa, expected_email, "plus");
defer gpa.free(fake_auth);
try tmp.dir.writeFile(.{ .sub_path = "fake-auth.json", .data = fake_auth });
try writeStrictExistingCodexHomeFakeCodex(tmp.dir);

const fake_bin_path = try fs.path.join(gpa, &[_][]const u8{ home_root, "fake-bin" });
defer gpa.free(fake_bin_path);
const path_override = try prependPathEntryAlloc(gpa, fake_bin_path);
defer gpa.free(path_override);

const result = try runCliWithIsolatedHomeAndPath(
gpa,
project_root,
home_root,
path_override,
&[_][]const u8{ "login", "--device-auth" },
);
defer gpa.free(result.stdout);
defer gpa.free(result.stderr);

try expectSuccess(result);

const codex_home = try codexHomeAlloc(gpa, home_root);
defer gpa.free(codex_home);

const fake_codex_home_path = try fs.path.join(gpa, &[_][]const u8{ home_root, "fake-codex-home.txt" });
defer gpa.free(fake_codex_home_path);
const fake_codex_home_data = try fixtures.readFileAlloc(gpa, fake_codex_home_path);
defer gpa.free(fake_codex_home_data);
const fake_codex_home = std.mem.trim(u8, fake_codex_home_data, " \r\n");
try std.testing.expect(!std.mem.eql(u8, fake_codex_home, codex_home));
try std.testing.expect(std.mem.indexOf(u8, fake_codex_home, "login-") != null);
try std.testing.expectError(error.FileNotFound, fs.cwd().access(fake_codex_home, .{}));

var loaded = try registry.loadRegistry(gpa, codex_home);
defer loaded.deinit(gpa);
try std.testing.expectEqual(@as(usize, 1), loaded.accounts.items.len);
try std.testing.expect(std.mem.eql(u8, loaded.accounts.items[0].email, expected_email));
}

test "Scenario: Given refreshed active auth before login when running login then old account snapshot is synced first" {
const gpa = std.testing.allocator;
const project_root = try projectRootAlloc(gpa);
Expand Down
Loading