Skip to content

Commit a1628fe

Browse files
committed
test(ci): add opencode autoconnect e2e
1 parent 2314235 commit a1628fe

2 files changed

Lines changed: 155 additions & 0 deletions

File tree

.github/workflows/check.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,16 @@ jobs:
8484
run: pnpm --filter ./packages/app lint:effect
8585
- name: Lint Effect-TS (lib)
8686
run: pnpm --filter ./packages/lib lint:effect
87+
88+
e2e-opencode:
89+
name: E2E (OpenCode)
90+
runs-on: ubuntu-latest
91+
timeout-minutes: 25
92+
steps:
93+
- uses: actions/checkout@v6
94+
- name: Install dependencies
95+
uses: ./.github/actions/setup
96+
- name: Docker info
97+
run: docker version && docker compose version
98+
- name: OpenCode autoconnect
99+
run: bash scripts/e2e/opencode-autoconnect.sh
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
RUN_ID="$(date +%s)-$RANDOM"
5+
ROOT="$(mktemp -d)"
6+
KEEP="${KEEP:-0}"
7+
8+
# Keep compose project/volume names unique to avoid interfering with any local docker-git state.
9+
OUT_DIR_REL=".docker-git/e2e/opencode-autoconnect-$RUN_ID"
10+
OUT_DIR="$ROOT/e2e/opencode-autoconnect-$RUN_ID"
11+
CONTAINER_NAME="dg-e2e-opencode-$RUN_ID"
12+
SERVICE_NAME="dg-e2e-opencode-$RUN_ID"
13+
VOLUME_NAME="dg-e2e-opencode-$RUN_ID-home"
14+
SSH_PORT="$(( (RANDOM % 1000) + 20000 ))"
15+
16+
export DOCKER_GIT_PROJECTS_ROOT="$ROOT"
17+
export DOCKER_GIT_STATE_AUTO_SYNC=0
18+
19+
on_error() {
20+
local line="$1"
21+
echo "e2e/opencode-autoconnect: failed at line $line" >&2
22+
docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' | head -n 50 || true
23+
if docker ps -a --format '{{.Names}}' | grep -qx "$CONTAINER_NAME" 2>/dev/null; then
24+
docker exec -u dev "$CONTAINER_NAME" bash -lc '
25+
echo "--- auth mounts ---"
26+
ls -la ~/.codex ~/.codex-shared ~/.local/share/opencode 2>/dev/null || true
27+
echo "--- opencode auth link ---"
28+
readlink -v ~/.local/share/opencode/auth.json 2>/dev/null || true
29+
echo "--- codex shared auth ---"
30+
ls -la ~/.codex-shared/auth.json 2>/dev/null || true
31+
echo "--- opencode shared auth ---"
32+
ls -la ~/.codex-shared/opencode 2>/dev/null || true
33+
ls -la ~/.codex-shared/opencode/auth.json 2>/dev/null || true
34+
' || true
35+
fi
36+
if [[ -d "$OUT_DIR" ]] && [[ -f "$OUT_DIR/docker-compose.yml" ]]; then
37+
(cd "$OUT_DIR" && docker compose ps) || true
38+
(cd "$OUT_DIR" && docker compose logs --no-color --tail 200) || true
39+
fi
40+
}
41+
42+
cleanup() {
43+
if [[ "$KEEP" == "1" ]]; then
44+
echo "e2e/opencode-autoconnect: KEEP=1 set; preserving temp dir: $ROOT" >&2
45+
echo "e2e/opencode-autoconnect: container name: $CONTAINER_NAME" >&2
46+
echo "e2e/opencode-autoconnect: out dir: $OUT_DIR" >&2
47+
return
48+
fi
49+
if [[ -d "$OUT_DIR" ]] && [[ -f "$OUT_DIR/docker-compose.yml" ]]; then
50+
(cd "$OUT_DIR" && docker compose down -v --remove-orphans) >/dev/null 2>&1 || true
51+
fi
52+
rm -rf "$ROOT" >/dev/null 2>&1 || true
53+
}
54+
55+
trap 'on_error $LINENO' ERR
56+
trap cleanup EXIT
57+
58+
# Ensure docker mounts a file (not an auto-created directory).
59+
mkdir -p "$ROOT/.orch/auth/codex" "$ROOT/.orch/env"
60+
: > "$ROOT/authorized_keys"
61+
62+
# Seed a fake (but structurally valid) Codex auth.json so the entrypoint can
63+
# auto-connect OpenCode without manual /connect.
64+
node <<'NODE' > "$ROOT/.orch/auth/codex/auth.json"
65+
const now = Math.floor(Date.now() / 1000)
66+
const b64 = (obj) => Buffer.from(JSON.stringify(obj)).toString("base64url")
67+
const jwt = (payload) => `${b64({ alg: "none", typ: "JWT" })}.${b64(payload)}.sig`
68+
69+
const access = jwt({ exp: now + 3600, chatgpt_account_id: "org_test" })
70+
const idToken = jwt({ exp: now + 3600, email: "ci@example.com" })
71+
72+
const auth = {
73+
auth_mode: "chatgpt",
74+
OPENAI_API_KEY: null,
75+
tokens: {
76+
id_token: idToken,
77+
access_token: access,
78+
refresh_token: "refresh_test",
79+
account_id: "org_test"
80+
},
81+
last_refresh: new Date().toISOString()
82+
}
83+
84+
process.stdout.write(JSON.stringify(auth, null, 2))
85+
NODE
86+
87+
# Keep the container startup deterministic and fast for CI.
88+
mkdir -p "$OUT_DIR/.orch/env"
89+
cat > "$OUT_DIR/.orch/env/project.env" <<'EOF_ENV'
90+
# docker-git project env (e2e)
91+
CODEX_AUTO_UPDATE=0
92+
CODEX_SHARE_AUTH=1
93+
OPENCODE_SHARE_AUTH=1
94+
OPENCODE_AUTO_CONNECT=1
95+
EOF_ENV
96+
97+
pnpm run docker-git clone https://github.com/octocat/Hello-World \
98+
--force \
99+
--repo-ref master \
100+
--ssh-port "$SSH_PORT" \
101+
--out-dir "$OUT_DIR_REL" \
102+
--container-name "$CONTAINER_NAME" \
103+
--service-name "$SERVICE_NAME" \
104+
--volume-name "$VOLUME_NAME"
105+
106+
# Basic sanity checks.
107+
docker ps --format '{{.Names}}' | grep -qx "$CONTAINER_NAME"
108+
109+
docker exec "$CONTAINER_NAME" opencode --version >/dev/null
110+
docker exec -u dev "$CONTAINER_NAME" oh-my-opencode --version >/dev/null
111+
112+
docker exec -u dev "$CONTAINER_NAME" bash -lc \
113+
'test -f ~/.config/opencode/opencode.json && grep -q "oh-my-opencode" ~/.config/opencode/opencode.json'
114+
115+
docker exec -u dev "$CONTAINER_NAME" bash -lc \
116+
'test "$(readlink ~/.local/share/opencode/auth.json)" = "/home/dev/.codex-shared/opencode/auth.json"'
117+
118+
docker exec -u dev "$CONTAINER_NAME" bash -lc 'test -f ~/.codex-shared/auth.json'
119+
120+
docker exec -u dev "$CONTAINER_NAME" bash -lc \
121+
'node - <<'\''NODE'\''
122+
const fs = require("fs")
123+
124+
const p = process.env.HOME + "/.local/share/opencode/auth.json"
125+
const auth = JSON.parse(fs.readFileSync(p, "utf8"))
126+
const openai = auth && auth.openai
127+
if (!openai) process.exit(1)
128+
if (openai.type === "oauth") {
129+
if (typeof openai.access !== "string" || openai.access.length === 0) process.exit(1)
130+
if (typeof openai.refresh !== "string" || openai.refresh.length === 0) process.exit(1)
131+
if (typeof openai.expires !== "number") process.exit(1)
132+
process.exit(0)
133+
}
134+
if (openai.type === "api") {
135+
if (typeof openai.key !== "string" || openai.key.length === 0) process.exit(1)
136+
process.exit(0)
137+
}
138+
process.exit(1)
139+
NODE'
140+
141+
# Exercises Bun-based plugin install path (regression test for BUN_INSTALL env).
142+
docker exec -u dev "$CONTAINER_NAME" opencode models openai | grep -m 1 -E '^openai/' >/dev/null

0 commit comments

Comments
 (0)