3ba6d6784c
Adds scripts/bridged-launchd-wrapper.sh so the launchd-run daemon still gets WORKER_GITEA_TOKEN/AI_GATEWAY_TOKEN by execing through a login shell (launchd never sources secrets.sh itself). bridged now logs at startup which required token env vars (derived from each profile's tokenEnv/gitTokenEnv, not a hand-written list) resolved or are MISSING, by name only. Fills in the real paths in deploy/dev.ltms.bridged.plist for this host and points it at the wrapper. scripts/redeploy-bridged.sh now detects a loaded launchd agent and uses launchctl unload/load instead of a raw kill+nohup, because a bare SIGTERM exits this JVM at 143 (measured) which KeepAlive.SuccessfulExit=false reads as a crash and would race the script's own restart; --check reports installed/loaded state and stays read-only.
115 lines
4.1 KiB
Java
115 lines
4.1 KiB
Java
package dev.ltms.bridged;
|
|
|
|
import dev.ltms.bridged.config.BridgedConfig;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.api.io.TempDir;
|
|
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
/**
|
|
* CB-594: {@link Bridged#requiredSecretEnvVars(BridgedConfig)} is what decides what the startup
|
|
* secret report checks — it must derive that set from the config, not a hand-written list, or a
|
|
* new profile's token silently stops being reported.
|
|
*/
|
|
class RequiredSecretEnvVarsTest {
|
|
|
|
private static BridgedConfig load(Path dir, String yaml) throws Exception {
|
|
Path f = dir.resolve("bridged.yaml");
|
|
Files.writeString(f, yaml);
|
|
return BridgedConfig.load(f);
|
|
}
|
|
|
|
@Test
|
|
void collectsATokenEnvPerNonSubscriptionProfile(@TempDir Path dir) throws Exception {
|
|
BridgedConfig cfg = load(dir, """
|
|
profiles:
|
|
local:
|
|
baseUrl: http://gx00.gw:8000
|
|
tokenEnv: AI_GATEWAY_TOKEN
|
|
""");
|
|
|
|
Map<String, List<String>> required = Bridged.requiredSecretEnvVars(cfg);
|
|
|
|
assertTrue(required.containsKey("AI_GATEWAY_TOKEN"));
|
|
assertEquals(List.of("profile 'local' tokenEnv"), required.get("AI_GATEWAY_TOKEN"));
|
|
}
|
|
|
|
@Test
|
|
void aSubscriptionProfileNeedsNoTokenEnv(@TempDir Path dir) throws Exception {
|
|
BridgedConfig cfg = load(dir, """
|
|
profiles:
|
|
opus:
|
|
subscription: true
|
|
model: claude-opus-5
|
|
""");
|
|
|
|
assertTrue(Bridged.requiredSecretEnvVars(cfg).isEmpty(),
|
|
"subscription: true never reads ANTHROPIC_AUTH_TOKEN — see Profile#isSubscription");
|
|
}
|
|
|
|
@Test
|
|
void gitTokenEnvIsOptInAndCollectedWhenSet(@TempDir Path dir) throws Exception {
|
|
BridgedConfig cfg = load(dir, """
|
|
profiles:
|
|
local:
|
|
baseUrl: http://gx00.gw:8000
|
|
tokenEnv: AI_GATEWAY_TOKEN
|
|
gitTokenEnv: WORKER_GITEA_TOKEN
|
|
""");
|
|
|
|
Map<String, List<String>> required = Bridged.requiredSecretEnvVars(cfg);
|
|
|
|
assertTrue(required.containsKey("WORKER_GITEA_TOKEN"));
|
|
assertEquals(List.of("profile 'local' gitTokenEnv"), required.get("WORKER_GITEA_TOKEN"));
|
|
}
|
|
|
|
@Test
|
|
void noGitTokenEnvMeansNothingIsRequiredForIt(@TempDir Path dir) throws Exception {
|
|
BridgedConfig cfg = load(dir, """
|
|
profiles:
|
|
local:
|
|
baseUrl: http://gx00.gw:8000
|
|
tokenEnv: AI_GATEWAY_TOKEN
|
|
""");
|
|
|
|
assertFalse(Bridged.requiredSecretEnvVars(cfg).containsKey("WORKER_GITEA_TOKEN"));
|
|
}
|
|
|
|
@Test
|
|
void aVarSharedByTwoProfilesIsReportedOnceNamingBoth(@TempDir Path dir) throws Exception {
|
|
BridgedConfig cfg = load(dir, """
|
|
profiles:
|
|
local:
|
|
baseUrl: http://gx00.gw:8000
|
|
tokenEnv: AI_GATEWAY_TOKEN
|
|
gitTokenEnv: WORKER_GITEA_TOKEN
|
|
gx:
|
|
kind: opencode
|
|
baseUrl: https://llm.ltms.dev/v1
|
|
tokenEnv: AI_GATEWAY_TOKEN
|
|
gitTokenEnv: WORKER_GITEA_TOKEN
|
|
""");
|
|
|
|
Map<String, List<String>> required = Bridged.requiredSecretEnvVars(cfg);
|
|
|
|
assertEquals(List.of("profile 'local' tokenEnv", "profile 'gx' tokenEnv"),
|
|
required.get("AI_GATEWAY_TOKEN"));
|
|
assertEquals(List.of("profile 'local' gitTokenEnv", "profile 'gx' gitTokenEnv"),
|
|
required.get("WORKER_GITEA_TOKEN"));
|
|
}
|
|
|
|
@Test
|
|
void noProfilesMeansNothingIsRequired(@TempDir Path dir) throws Exception {
|
|
BridgedConfig cfg = load(dir, "bind:\n host: 127.0.0.1\n port: 8765\n");
|
|
|
|
assertTrue(Bridged.requiredSecretEnvVars(cfg).isEmpty());
|
|
}
|
|
}
|