Skip to content

Commit e5a7eed

Browse files
Refactor: Improve organization resolution for dart symbol map upload
Co-authored-by: daniel.szoke <daniel.szoke@sentry.io>
1 parent 9c1cd63 commit e5a7eed

4 files changed

Lines changed: 118 additions & 21 deletions

File tree

src/commands/dart_symbol_map/upload.rs

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -113,26 +113,22 @@ pub(super) fn execute(args: DartSymbolMapUploadArgs) -> Result<()> {
113113

114114
// Prepare chunked upload
115115
let api = Api::current();
116-
// Resolve org and project like logs: prefer args, fallback to defaults
116+
// Resolve org and project, with org also checking auth token payload
117117
let config = Config::current();
118-
let (default_org, default_project) = config.get_org_and_project_defaults();
119-
let org = args
120-
.org
121-
.as_ref()
122-
.or(default_org.as_ref())
123-
.ok_or_else(|| anyhow::anyhow!(
124-
"No organization specified. Please specify an organization using the --org argument."
125-
))?;
118+
let org = config.get_org_with_cli_input(args.org.as_deref())?;
126119
let project = args
127120
.project
128-
.as_ref()
129-
.or(default_project.as_ref())
130-
.ok_or_else(|| anyhow::anyhow!(
131-
"No project specified. Use --project or set a default in config."
132-
))?;
121+
.clone()
122+
.or_else(|| std::env::var("SENTRY_PROJECT").ok())
123+
.or_else(|| config.get_project_default().ok())
124+
.ok_or_else(|| {
125+
anyhow::anyhow!(
126+
"No project specified. Use --project or set a default in config."
127+
)
128+
})?;
133129
let chunk_upload_options = api
134130
.authenticated()?
135-
.get_chunk_upload_options(org)?;
131+
.get_chunk_upload_options(&org)?;
136132

137133

138134
// Early file size check against server or default limits (same as debug files)
@@ -148,7 +144,7 @@ pub(super) fn execute(args: DartSymbolMapUploadArgs) -> Result<()> {
148144
);
149145
}
150146

151-
let options = ChunkOptions::new(chunk_upload_options, org, project)
147+
let options = ChunkOptions::new(chunk_upload_options, &org, &project)
152148
.with_max_wait(DEFAULT_MAX_WAIT);
153149

154150
let chunked = Chunked::from(object, options.server_options().chunk_size);

src/config.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -320,13 +320,17 @@ impl Config {
320320
}
321321
}
322322

323-
/// Given a match object from clap, this returns the org from it.
324-
pub fn get_org(&self, matches: &ArgMatches) -> Result<String> {
323+
/// Resolves the org from CLI input, environment variables, config, and auth token.
324+
///
325+
/// The resolution order is:
326+
/// 1. Org embedded in auth token (takes precedence, with warning if different from CLI)
327+
/// 2. CLI argument or SENTRY_ORG environment variable
328+
/// 3. Config file defaults
329+
pub fn get_org_with_cli_input(&self, cli_org: Option<&str>) -> Result<String> {
325330
let org_from_token = self.cached_token_data.as_ref().map(|t| &t.org);
326331

327-
let org_from_cli = matches
328-
.get_one::<String>("org")
329-
.cloned()
332+
let org_from_cli = cli_org
333+
.map(str::to_owned)
330334
.or_else(|| env::var("SENTRY_ORG").ok());
331335

332336
match (org_from_token, org_from_cli) {
@@ -352,6 +356,12 @@ impl Config {
352356
}
353357
}
354358

359+
/// Given a match object from clap, this returns the org from it.
360+
pub fn get_org(&self, matches: &ArgMatches) -> Result<String> {
361+
let cli_org = matches.get_one::<String>("org").map(String::as_str);
362+
self.get_org_with_cli_input(cli_org)
363+
}
364+
355365
/// Given a match object from clap, this returns the release from it.
356366
pub fn get_release(&self, matches: &ArgMatches) -> Result<String> {
357367
matches

tests/integration/test_utils/test_manager.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,16 @@ impl AssertCmdTestManager {
205205
self
206206
}
207207

208+
/// Set a custom environment variable for the test.
209+
pub fn env(
210+
mut self,
211+
key: impl AsRef<std::ffi::OsStr>,
212+
value: impl AsRef<std::ffi::OsStr>,
213+
) -> Self {
214+
self.command.env(key, value);
215+
self
216+
}
217+
208218
/// Run the command and perform assertions.
209219
///
210220
/// This function asserts both the mocks and the command result.

tests/integration/upload_dart_symbol_map.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ use std::sync::atomic::{AtomicU8, Ordering};
33
use crate::integration::test_utils::AssertCommand;
44
use crate::integration::{MockEndpointBuilder, TestManager};
55

6+
/// A test org auth token with org="wat-org" and empty URL.
7+
/// Format: sntrys_{base64_payload}_{base64_secret}
8+
/// Payload: {"iat":1704374159.069583,"url":"","region_url":"","org":"wat-org"}
9+
const ORG_AUTH_TOKEN_WAT_ORG: &str = "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";
10+
611
#[test]
712
fn command_upload_dart_symbol_map_missing_capability() {
813
// Server does not advertise `dartsymbolmap` capability → command should bail early.
@@ -102,3 +107,79 @@ fn command_upload_dart_symbol_map_invalid_mapping() {
102107
.with_default_token()
103108
.run_and_assert(AssertCommand::Failure);
104109
}
110+
111+
#[test]
112+
fn command_upload_dart_symbol_map_org_from_token() {
113+
// When no --org is provided and SENTRY_ORG is not set, the org should be resolved
114+
// from the org auth token. This test verifies the fix for CLI-260.
115+
//
116+
// The test uses an org auth token with org="wat-org" (matching mock server paths).
117+
// By unsetting SENTRY_ORG and not providing --org, we verify the org is extracted
118+
// from the token.
119+
let call_count = AtomicU8::new(0);
120+
121+
TestManager::new()
122+
// Server advertises capability including `dartsymbolmap`.
123+
// This endpoint uses "wat-org" in the path - if org resolution fails,
124+
// the request would go to a different path and not match.
125+
.mock_endpoint(
126+
MockEndpointBuilder::new("GET", "/api/0/organizations/wat-org/chunk-upload/")
127+
.with_response_file("dart_symbol_map/get-chunk-upload.json"),
128+
)
129+
// Accept chunk upload requests for the missing chunks
130+
.mock_endpoint(MockEndpointBuilder::new(
131+
"POST",
132+
"/api/0/organizations/wat-org/chunk-upload/",
133+
))
134+
// Assemble flow: 1) not_found (missingChunks), 2) created, 3) ok
135+
.mock_endpoint(
136+
MockEndpointBuilder::new(
137+
"POST",
138+
"/api/0/projects/wat-org/wat-project/files/difs/assemble/",
139+
)
140+
.with_header_matcher("content-type", "application/json")
141+
.with_response_fn(move |request| {
142+
let body = request.body().expect("body should be readable");
143+
let body_json: serde_json::Value =
144+
serde_json::from_slice(body).expect("request body should be valid JSON");
145+
146+
let (checksum, _obj) = body_json
147+
.as_object()
148+
.and_then(|m| m.iter().next())
149+
.map(|(k, v)| (k.clone(), v.clone()))
150+
.expect("assemble request must contain at least one object");
151+
152+
match call_count.fetch_add(1, Ordering::Relaxed) {
153+
0 => format!(
154+
"{{\"{checksum}\":{{\"state\":\"not_found\",\"missingChunks\":[\"{checksum}\"]}}}}"
155+
)
156+
.into(),
157+
1 => format!(
158+
"{{\"{checksum}\":{{\"state\":\"created\",\"missingChunks\":[]}}}}"
159+
)
160+
.into(),
161+
2 => format!(
162+
"{{\"{checksum}\":{{\"state\":\"ok\",\"detail\":null,\"missingChunks\":[],\"dif\":{{\"id\":\"1\",\"uuid\":\"00000000-0000-0000-0000-000000000000\",\"debugId\":\"00000000-0000-0000-0000-000000000000\",\"objectName\":\"dartsymbolmap.json\",\"cpuName\":\"any\",\"headers\":{{\"Content-Type\":\"application/octet-stream\"}},\"size\":1,\"sha1\":\"{checksum}\",\"dateCreated\":\"1776-07-04T12:00:00.000Z\",\"data\":{{}}}}}}}}"
163+
)
164+
.into(),
165+
n => panic!(
166+
"Only 3 calls to the assemble endpoint expected, but there were {}.",
167+
n + 1
168+
),
169+
}
170+
})
171+
.expect(3),
172+
)
173+
.assert_cmd([
174+
"dart-symbol-map",
175+
"upload",
176+
// No --org flag provided!
177+
"tests/integration/_fixtures/dart_symbol_map/dartsymbolmap.json",
178+
"tests/integration/_fixtures/Sentry.Samples.Console.Basic.pdb",
179+
])
180+
// Use org auth token with embedded org="wat-org" instead of default token
181+
.env("SENTRY_AUTH_TOKEN", ORG_AUTH_TOKEN_WAT_ORG)
182+
// Explicitly unset SENTRY_ORG to ensure org comes from token
183+
.env("SENTRY_ORG", "")
184+
.run_and_assert(AssertCommand::Success);
185+
}

0 commit comments

Comments
 (0)