Skip to content

Commit fe2fc4e

Browse files
committed
cicd fixes
1 parent ffcc712 commit fe2fc4e

8 files changed

Lines changed: 101 additions & 60 deletions

File tree

‎.github/workflows/integration-tests.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,10 +209,13 @@ jobs:
209209
OPERATOR_GITHUB_TOKEN: ${{ secrets.OPERATOR_GITHUB_TOKEN }}
210210
GH_TOKEN: ${{ secrets.OPERATOR_GITHUB_TOKEN }}
211211
GITLAB_TOKEN: ${{ secrets.OPERATOR_GITLAB_TOKEN }}
212+
GITEA_TOKEN: ${{ secrets.OPERATOR_GITEA_TOKEN }}
212213
OPERATOR_GITPROVIDER_TEST_REPO_GITHUB: ${{ vars.OPERATOR_GITPROVIDER_TEST_REPO_GITHUB || '' }}
213214
OPERATOR_GITPROVIDER_TEST_PR_GITHUB: ${{ vars.OPERATOR_GITPROVIDER_TEST_PR_GITHUB || '' }}
214215
OPERATOR_GITPROVIDER_TEST_REPO_GITLAB: ${{ vars.OPERATOR_GITPROVIDER_TEST_REPO_GITLAB || '' }}
215216
OPERATOR_GITPROVIDER_TEST_PR_GITLAB: ${{ vars.OPERATOR_GITPROVIDER_TEST_PR_GITLAB || '' }}
217+
OPERATOR_GITPROVIDER_TEST_REPO_GITEA: ${{ vars.OPERATOR_GITPROVIDER_TEST_REPO_GITEA || '' }}
218+
OPERATOR_GITPROVIDER_TEST_PR_GITEA: ${{ vars.OPERATOR_GITPROVIDER_TEST_PR_GITEA || '' }}
216219
run: cargo test --locked --test gitprovider_integration -- --nocapture
217220

218221
- name: Cleanup optest branches

‎bump-version.sh‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ TEXT_FILES=(
4040
# JSON files: update .version via jq
4141
JSON_FILES=(
4242
"vscode-extension/package.json"
43-
"backstage-server/package.json"
4443
"agnt-plugin/package.json"
4544
"agnt-plugin/manifest.json"
4645
)

‎charts/operator/Chart.yaml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ apiVersion: v2
22
name: operator
33
description: Run Operator as a single-writer agent orchestration service
44
type: application
5-
version: 0.2.6
6-
appVersion: "0.2.6"
5+
version: 0.2.7
6+
appVersion: "0.2.7"
77
kubeVersion: ">=1.25.0-0"
88
home: https://operator.untra.io
99
sources:

‎coder-module/main.tf‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ variable "slug" {
3838
variable "install_version" {
3939
type = string
4040
description = "The version of operator to install (must match a GitHub release tag)."
41-
default = "0.2.6"
41+
default = "0.2.7"
4242
}
4343

4444
variable "install_prefix" {

‎opr8r/src/api.rs‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -399,14 +399,16 @@ mod tests {
399399
// sending `Authorization: Bearer ` would be worse than sending nothing.
400400
for blank in ["", " ", "\n"] {
401401
let client = ApiClient::with_token("http://localhost:7008", Some(blank.to_string()));
402-
assert!(client.token.is_none(), "blank token {blank:?} should be dropped");
402+
assert!(
403+
client.token.is_none(),
404+
"blank token {blank:?} should be dropped"
405+
);
403406
}
404407
}
405408

406409
#[test]
407410
fn test_token_is_retained_when_supplied() {
408-
let client =
409-
ApiClient::with_token("http://localhost:7008", Some("cb-token".to_string()));
411+
let client = ApiClient::with_token("http://localhost:7008", Some("cb-token".to_string()));
410412
assert_eq!(client.token.as_deref(), Some("cb-token"));
411413
}
412414

‎scripts/cicdprep.sh‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,23 +173,20 @@ needs_docs() { has_changes '^(docs/|src/docs_gen/|src/taxonomy/taxonomy\.t
173173
needs_bun_root() { has_changes '^(package\.json|bun\.lock)$'; }
174174
needs_bun_ui() { has_changes '^ui/(package\.json|bun\.lock)$'; }
175175
needs_bun_webcomp() { has_changes '^webcomponents/(package\.json|bun\.lock)$'; }
176-
needs_bun_backstage() { has_changes '^backstage-server/(.*/)?(package\.json|bun\.lock)$'; }
177176

178177
# --- 0. Bun lockfiles ---
179178
#
180179
# Run this first, cheaply, across every bun project so a stale lockfile fails
181180
# loudly and early instead of midway through a UI build. Mirrors CI's
182181
# `bun install --frozen-lockfile`, and additionally covers the root and
183-
# backstage-server lockfiles that CI does not currently enforce.
184182

185-
if needs_bun_root || needs_bun_ui || needs_bun_webcomp || needs_bun_backstage; then
183+
if needs_bun_root || needs_bun_ui || needs_bun_webcomp ; then
186184
section "Bun lockfiles"
187185
require_tool bun "bun lockfile sync"
188186

189187
if needs_bun_root; then check_bun_lockfile "."; else skip "Lockfile sync: . (no changes)"; fi
190188
if needs_bun_ui; then check_bun_lockfile "ui"; else skip "Lockfile sync: ui (no changes)"; fi
191189
if needs_bun_webcomp; then check_bun_lockfile "webcomponents"; else skip "Lockfile sync: webcomponents (no changes)"; fi
192-
if needs_bun_backstage; then check_bun_lockfile "backstage-server"; else skip "Lockfile sync: backstage-server (no changes)"; fi
193190
else
194191
skip "Bun lockfiles"
195192
fi

‎tests/gitprovider_integration.rs‎

Lines changed: 89 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
//! Live integration tests for the `PrService` stack (GitHub, GitLab, ...)
1+
//! Live integration tests for the `PrService` stack (GitHub, GitLab, Gitea)
22
//!
3-
//! These tests drive the real provider CLIs (`gh`, `glab`) through
3+
//! These tests drive the real provider CLIs (`gh`, `glab`, `tea`) through
44
//! `pr_service_for`/`PrService` against designated, real, external test
55
//! repositories. They are strictly read-only: no create/write operations
66
//! against any provider are performed anywhere in this file.
@@ -17,20 +17,30 @@
1717
//! test repo.
1818
//! - `OPERATOR_GITPROVIDER_TEST_PR_GITLAB`: Number of an open GitLab MR on
1919
//! that repo with at least one comment.
20+
//! - `OPERATOR_GITPROVIDER_TEST_REPO_GITEA`: Full remote URL of a Gitea test
21+
//! repo. Gitea is self-hosted, so its origin also registers the Gitea host.
22+
//! - `OPERATOR_GITPROVIDER_TEST_PR_GITEA`: Number of an open Gitea PR on that
23+
//! repo with at least one comment.
2024
//!
21-
//! Each row additionally needs its CLI (`gh`, `glab`) installed and
25+
//! Each row additionally needs its CLI (`gh`, `glab`, `tea`) installed and
2226
//! authenticated. A row skips itself (with `eprintln!`) rather than failing
2327
//! the suite when its repo env var is unset or its CLI is unavailable.
2428
//!
29+
//! A row env var counts as unset when it is missing **or blank**: CI wires
30+
//! these from repository variables that export an empty string when
31+
//! undefined, and a blank value must skip the row rather than be parsed.
32+
//!
2533
//! ## Running Tests
2634
//!
2735
//! ```bash
28-
//! # Both rows
36+
//! # Every row
2937
//! OPERATOR_GITPROVIDER_TEST_ENABLED=true \
3038
//! OPERATOR_GITPROVIDER_TEST_REPO_GITHUB=https://github.com/owner/repo \
3139
//! OPERATOR_GITPROVIDER_TEST_PR_GITHUB=1 \
3240
//! OPERATOR_GITPROVIDER_TEST_REPO_GITLAB=https://gitlab.com/owner/repo \
3341
//! OPERATOR_GITPROVIDER_TEST_PR_GITLAB=1 \
42+
//! OPERATOR_GITPROVIDER_TEST_REPO_GITEA=https://gitea.example/owner/repo \
43+
//! OPERATOR_GITPROVIDER_TEST_PR_GITEA=1 \
3444
//! cargo test --test gitprovider_integration -- --nocapture
3545
//!
3646
//! # Table-invariant unit test only (no env vars, no CLIs needed)
@@ -39,6 +49,7 @@
3949
4050
use operator::api::pr_service::pr_service_for;
4151
use operator::api::PrService;
52+
use operator::config::GitConfig;
4253
use operator::types::pr::{GitProvider, PrState, RepoInfo};
4354
use std::collections::HashSet;
4455
use std::env;
@@ -53,6 +64,17 @@ fn gitprovider_tests_enabled() -> bool {
5364
.unwrap_or(false)
5465
}
5566

67+
/// A set-but-blank value reads as unset -- see the blank-value note in the
68+
/// module docs.
69+
fn non_blank(value: String) -> Option<String> {
70+
(!value.trim().is_empty()).then_some(value)
71+
}
72+
73+
/// The value of `var`, or `None` when it is unset or blank.
74+
fn configured(var: &str) -> Option<String> {
75+
env::var(var).ok().and_then(non_blank)
76+
}
77+
5678
/// Macro to skip test if git-provider tests are not configured
5779
macro_rules! skip_if_not_configured {
5880
() => {
@@ -66,7 +88,7 @@ macro_rules! skip_if_not_configured {
6688
// ─── Provider Table ──────────────────────────────────────────────────────────
6789

6890
/// One row of the live provider test table. Adding a new operational
69-
/// provider (bitbucket, azure, forgejo, gitea) is just adding a row here
91+
/// provider (bitbucket, azure, forgejo) is just adding a row here
7092
/// plus a `#[tokio::test]` that calls `run_provider_case` for it -- see the
7193
/// template comment below `PROVIDER_CASES`.
7294
struct ProviderCase {
@@ -78,8 +100,12 @@ struct ProviderCase {
78100
repo_env: &'static str,
79101
/// Env var holding the PR/MR number to exercise (must have >=1 comment).
80102
pr_env: &'static str,
103+
/// Registers the test repo's origin as this provider's host. `None` for
104+
/// providers resolved by well-known domain; `Some` for self-hosted ones,
105+
/// which are unresolvable until their host is configured.
106+
apply_host: Option<fn(&mut GitConfig, String)>,
81107
/// Builds the `PrService` under test for this row.
82-
service: fn() -> Arc<dyn PrService>,
108+
service: fn(&GitConfig) -> Arc<dyn PrService>,
83109
}
84110

85111
const PROVIDER_CASES: &[ProviderCase] = &[
@@ -88,34 +114,24 @@ const PROVIDER_CASES: &[ProviderCase] = &[
88114
cli: "gh",
89115
repo_env: "OPERATOR_GITPROVIDER_TEST_REPO_GITHUB",
90116
pr_env: "OPERATOR_GITPROVIDER_TEST_PR_GITHUB",
91-
service: || {
92-
pr_service_for(GitProvider::GitHub, &operator::config::GitConfig::default())
93-
.expect("github is operational")
94-
},
117+
apply_host: None,
118+
service: |git| pr_service_for(GitProvider::GitHub, git).expect("github is operational"),
95119
},
96120
ProviderCase {
97121
slug: "gitlab",
98122
cli: "glab",
99123
repo_env: "OPERATOR_GITPROVIDER_TEST_REPO_GITLAB",
100124
pr_env: "OPERATOR_GITPROVIDER_TEST_PR_GITLAB",
101-
service: || {
102-
pr_service_for(GitProvider::GitLab, &operator::config::GitConfig::default())
103-
.expect("gitlab is operational")
104-
},
125+
apply_host: None,
126+
service: |git| pr_service_for(GitProvider::GitLab, git).expect("gitlab is operational"),
105127
},
106128
ProviderCase {
107129
slug: "gitea",
108130
cli: "tea",
109131
repo_env: "OPERATOR_GITPROVIDER_TEST_REPO_GITEA",
110132
pr_env: "OPERATOR_GITPROVIDER_TEST_PR_GITEA",
111-
service: || {
112-
let mut config = operator::config::GiteaConfig::default();
113-
if let Ok(remote) = env::var("OPERATOR_GITPROVIDER_TEST_REPO_GITEA") {
114-
let url = url::Url::parse(&remote).expect("Gitea test remote must be HTTPS");
115-
config.host = Some(url.origin().ascii_serialization());
116-
}
117-
Arc::new(operator::api::gitea_service::GiteaService::new(config))
118-
},
133+
apply_host: Some(|git, origin| git.gitea.host = Some(origin)),
134+
service: |git| pr_service_for(GitProvider::Gitea, git).expect("gitea is operational"),
119135
},
120136
// Template for a future row (documentation only, NOT dead code -- copy
121137
// this into a real `ProviderCase` once the provider gets an operational
@@ -126,12 +142,13 @@ const PROVIDER_CASES: &[ProviderCase] = &[
126142
// cli: "bb" or whatever CLI backs BitbucketService,
127143
// repo_env: "OPERATOR_GITPROVIDER_TEST_REPO_BITBUCKET",
128144
// pr_env: "OPERATOR_GITPROVIDER_TEST_PR_BITBUCKET",
129-
// service: || pr_service_for(GitProvider::Bitbucket).expect("bitbucket is operational"),
145+
// apply_host: None,
146+
// service: |git| pr_service_for(GitProvider::Bitbucket, git).expect("operational"),
130147
// },
131148
//
132-
// Azure DevOps, Forgejo, and Gitea rows follow the same shape with
133-
// `GitProvider::AzureDevOps` / `Forgejo` / `Gitea` and their own
134-
// `OPERATOR_GITPROVIDER_TEST_{REPO,PR}_{AZUREDEVOPS,FORGEJO,GITEA}` pair.
149+
// Azure DevOps and Forgejo follow the same shape once `pr_service_for`
150+
// stops returning `UnsupportedProviderError` for them. A self-hosted row
151+
// also needs `apply_host`, as the `gitea` row above does.
135152
];
136153

137154
/// Look up a row by slug (stable against reordering `PROVIDER_CASES`).
@@ -144,19 +161,39 @@ fn case_by_slug(slug: &str) -> &'static ProviderCase {
144161

145162
// ─── Shared Row Runner ────────────────────────────────────────────────────────
146163

164+
/// The `GitConfig` a row's service and URL parsing both resolve against.
165+
/// Self-hosted rows register the live repo's own origin as their host.
166+
fn git_config_for(case: &ProviderCase, repo_url: &str) -> GitConfig {
167+
let mut git = GitConfig::default();
168+
if let Some(apply_host) = case.apply_host {
169+
let origin = url::Url::parse(repo_url)
170+
.unwrap_or_else(|e| {
171+
panic!(
172+
"{}: {} must be an absolute URL: {e}",
173+
case.slug, case.repo_env
174+
)
175+
})
176+
.origin()
177+
.ascii_serialization();
178+
apply_host(&mut git, origin);
179+
}
180+
git
181+
}
182+
147183
/// Drive one row of `PROVIDER_CASES` through the live `PrService` stack.
148184
/// Skips (with `eprintln!`) rather than fails when the row isn't configured
149185
/// or its CLI isn't installed/authed -- a missing row must not fail the
150186
/// suite.
151187
async fn run_provider_case(case: &ProviderCase) {
152188
skip_if_not_configured!();
153189

154-
let Ok(repo_url) = env::var(case.repo_env) else {
190+
let Some(repo_url) = configured(case.repo_env) else {
155191
eprintln!("Skipping {} row: {} not set", case.slug, case.repo_env);
156192
return;
157193
};
158194

159-
let service = (case.service)();
195+
let git = git_config_for(case, &repo_url);
196+
let service = (case.service)(&git);
160197

161198
// 1. check_available
162199
match service.check_available().await {
@@ -187,15 +224,6 @@ async fn run_provider_case(case: &ProviderCase) {
187224
eprintln!("{}: authenticated as {user}", case.slug);
188225

189226
// 3. RepoInfo::from_remote_url
190-
let mut git = operator::config::GitConfig::default();
191-
if case.slug == "gitea" {
192-
git.gitea.host = Some(
193-
url::Url::parse(&repo_url)
194-
.unwrap()
195-
.origin()
196-
.ascii_serialization(),
197-
);
198-
}
199227
let hosts = operator::types::pr::ProviderHosts::from_config(&git).unwrap();
200228
let repo_info = RepoInfo::from_remote_url_with_hosts(&repo_url, &hosts)
201229
.unwrap_or_else(|e| panic!("{}: failed to parse repo url {repo_url}: {e}", case.slug));
@@ -206,7 +234,7 @@ async fn run_provider_case(case: &ProviderCase) {
206234
case.slug
207235
);
208236

209-
let Ok(pr_number_str) = env::var(case.pr_env) else {
237+
let Some(pr_number_str) = configured(case.pr_env) else {
210238
eprintln!(
211239
"Skipping remaining {} assertions: {} not set",
212240
case.slug, case.pr_env
@@ -272,6 +300,11 @@ async fn test_gitlab_provider_live() {
272300
run_provider_case(case_by_slug("gitlab")).await;
273301
}
274302

303+
#[tokio::test]
304+
async fn test_gitea_provider_live() {
305+
run_provider_case(case_by_slug("gitea")).await;
306+
}
307+
275308
// ─── CLI Flag Contract (no credentials, no network) ──────────────────────────
276309

277310
/// Every long flag an argv builder emits must appear in the installed CLI's
@@ -344,6 +377,23 @@ fn provider_cli_flags_are_accepted_by_the_installed_binary() {
344377

345378
// ─── Table Invariants (plain `cargo test`, no gating) ────────────────────────
346379

380+
/// CI wires every row with `${{ vars.X || '' }}`, which exports an empty
381+
/// string when the variable is undefined -- a blank value must read as unset.
382+
#[test]
383+
fn blank_env_values_are_treated_as_unset() {
384+
for blank in ["", " ", "\n"] {
385+
assert_eq!(
386+
non_blank(blank.to_string()),
387+
None,
388+
"blank value {blank:?} should read as unset"
389+
);
390+
}
391+
assert_eq!(
392+
non_blank("https://github.com/owner/repo".to_string()).as_deref(),
393+
Some("https://github.com/owner/repo")
394+
);
395+
}
396+
347397
#[test]
348398
fn provider_case_table_env_names_unique_and_slugs_match() {
349399
let mut repo_envs = HashSet::new();
@@ -367,7 +417,7 @@ fn provider_case_table_env_names_unique_and_slugs_match() {
367417
case.slug
368418
);
369419

370-
let service = (case.service)();
420+
let service = (case.service)(&GitConfig::default());
371421
assert_eq!(
372422
service.provider_name(),
373423
case.slug,
@@ -376,8 +426,3 @@ fn provider_case_table_env_names_unique_and_slugs_match() {
376426
);
377427
}
378428
}
379-
380-
#[tokio::test]
381-
async fn gitea_provider_live() {
382-
run_provider_case(PROVIDER_CASES.iter().find(|c| c.slug == "gitea").unwrap()).await;
383-
}

‎vscode-extension/src/api-client.ts‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -648,11 +648,6 @@ export class OperatorApiClient {
648648
return this.request('/api/v1/queue/kanban');
649649
}
650650

651-
/** Kanban board columns — the API-backed source for the ticket trees. */
652-
async getKanban(): Promise<KanbanBoardResponse> {
653-
return this.getJson<KanbanBoardResponse>('/api/v1/queue/kanban');
654-
}
655-
656651
async listDelegators(): Promise<DelegatorsResponse> {
657652
return this.request('/api/v1/delegators');
658653
}

0 commit comments

Comments
 (0)