Compare commits

..
Author SHA1 Message Date
Graham SteffaniakandCursor 5512d188ac feat(upload): type uploadStallTimeoutSeconds end to end
- store/types: UserObject.fileLoading.uploadStallTimeoutSeconds
- store/mutations: default 10s for new + existing users
- utils/upload: progressTimeoutMs nullish-guards the option
- api/resources: document the onsent callback on post/postPublic signatures

Splits be2b16b0d's upload-stall hunk; base is 493f8f741 (post-merge lockfix).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-14 20:31:34 -06:00
7 changed files with 31 additions and 39 deletions

No files matched your search

-1
View File
@@ -93,7 +93,6 @@ func TestFillCenter_MatchesImagingFill(t *testing.T) {
for _, tg := range targets {
for fname, f := range filters {
t.Run(fmt.Sprintf("%s/%dx%d/%s", name, tg[0], tg[1], fname), func(t *testing.T) {
t.Parallel()
want := imaging.Fill(src, tg[0], tg[1], imaging.Center, f)
got := fillCenter(src, tg[0], tg[1], f)
require.Equal(t, fmt.Sprintf("%T", want), fmt.Sprintf("%T", got), "image type")
@@ -13,15 +13,14 @@ import (
func withMediaHandlerSettings(t *testing.T) {
t.Helper()
originalMedia := settings.CurrentMediaSettings()
originalTranscode := settings.Config.Integrations.Media.Transcode
originalFFmpeg := settings.Env.FFmpegAvailable
t.Cleanup(func() {
settings.Config.Integrations.Media.Transcode = originalTranscode
settings.Env.FFmpegAvailable = originalFFmpeg
settings.ApplyMediaSettings(originalMedia)
})
settings.Env.FFmpegAvailable = true
media := originalMedia
media.Transcode = settings.FullAdminTranscode{
settings.Config.Integrations.Media.Transcode = settings.FullAdminTranscode{
UserTranscode: settings.UserTranscode{
Enabled: true,
MaxConcurrentPerUser: 2,
@@ -29,7 +28,6 @@ func withMediaHandlerSettings(t *testing.T) {
MaxConcurrent: 3,
Profiles: settings.DefaultTranscodeProfiles(),
}
settings.ApplyMediaSettings(media)
}
func mediaHandlerContext(admin bool) *Context {
@@ -156,9 +154,7 @@ func TestSettingsMediaHandlerFullPayloadForAdmin(t *testing.T) {
// Disabling transcoding at runtime must be visible to non-admin players.
func TestSettingsMediaHandlerReflectsDisabledTranscode(t *testing.T) {
withMediaHandlerSettings(t)
disabled := settings.CurrentMediaSettings()
disabled.Transcode.Enabled = false
settings.ApplyMediaSettings(disabled)
settings.Config.Integrations.Media.Transcode.Enabled = false
transcode := objectField(t, callMediaGet(t, false), "transcode")
if enabled, ok := transcode["enabled"].(bool); !ok || enabled {
+17 -27
View File
@@ -13,31 +13,21 @@ import (
const settingsRedactSentinel = "SENTINEL-SETTINGS-SECRET"
func swapSettingsConfigForTest(t *testing.T, mutate func(*settings.Settings)) {
t.Helper()
cfg := settings.Config
if mutate != nil {
mutate(&cfg)
}
prev := settings.SwapConfigForTest(cfg)
t.Cleanup(func() { settings.SwapConfigForTest(prev) })
}
// TestSettingsGetHandler_redactsSecrets is the end-to-end regression guard for
// GHSA-892f-9h5p-mfc8: GET /api/settings (and ?property=auth) must not return cleartext secrets.
func TestSettingsGetHandler_redactsSecrets(t *testing.T) {
setupTestEnv(t)
swapSettingsConfigForTest(t, func(cfg *settings.Settings) {
cfg.Auth.Key = settingsRedactSentinel
cfg.Auth.TotpSecret = settingsRedactSentinel
cfg.Auth.Methods.PasswordAuth.AdminPassword = settingsRedactSentinel
cfg.Auth.Methods.PasswordAuth.Recaptcha.Secret = settingsRedactSentinel
cfg.Auth.Methods.OidcAuth.ClientSecret = settingsRedactSentinel
cfg.Auth.Methods.LdapAuth.UserPassword = settingsRedactSentinel
cfg.Auth.Methods.JwtAuth.Secret = settingsRedactSentinel
cfg.Integrations.OnlyOffice.Secret = settingsRedactSentinel
})
prev := settings.Config
t.Cleanup(func() { settings.Config = prev })
settings.Config.Auth.Key = settingsRedactSentinel
settings.Config.Auth.TotpSecret = settingsRedactSentinel
settings.Config.Auth.Methods.PasswordAuth.AdminPassword = settingsRedactSentinel
settings.Config.Auth.Methods.PasswordAuth.Recaptcha.Secret = settingsRedactSentinel
settings.Config.Auth.Methods.OidcAuth.ClientSecret = settingsRedactSentinel
settings.Config.Auth.Methods.LdapAuth.UserPassword = settingsRedactSentinel
settings.Config.Auth.Methods.JwtAuth.Secret = settingsRedactSentinel
settings.Config.Integrations.OnlyOffice.Secret = settingsRedactSentinel
admin := &users.User{
FrontendUser: users.FrontendUser{
@@ -72,9 +62,9 @@ func TestSettingsGetHandler_redactsSecrets(t *testing.T) {
func TestSettingsGetHandler_doesNotMutateLiveConfig(t *testing.T) {
setupTestEnv(t)
swapSettingsConfigForTest(t, func(cfg *settings.Settings) {
cfg.Auth.Key = settingsRedactSentinel
})
prev := settings.Config
t.Cleanup(func() { settings.Config = prev })
settings.Config.Auth.Key = settingsRedactSentinel
admin := &users.User{
FrontendUser: users.FrontendUser{
@@ -102,10 +92,10 @@ func TestSettingsGetHandler_doesNotMutateLiveConfig(t *testing.T) {
func TestSettingsGetHandler_keepsNonSecretAuthFields(t *testing.T) {
setupTestEnv(t)
swapSettingsConfigForTest(t, func(cfg *settings.Settings) {
cfg.Auth.Key = settingsRedactSentinel
cfg.Auth.TokenExpirationHours = 7
})
prev := settings.Config
t.Cleanup(func() { settings.Config = prev })
settings.Config.Auth.Key = settingsRedactSentinel
settings.Config.Auth.TokenExpirationHours = 7
admin := &users.User{
FrontendUser: users.FrontendUser{
+2
View File
@@ -804,6 +804,7 @@ async function downloadChunked(file, shareHash = "") {
* @param {((percent: number) => void) | undefined} [onupload]
* @param {Record<string, string | number>} [headers]
* @param {boolean} [isDir]
* @param {(() => void) | undefined} [onsent]
* @returns {Promise<any> & { xhr?: XMLHttpRequest | null, abort?: () => void }}
*/
export function post(
@@ -1431,6 +1432,7 @@ export function getTextPreviewURLPublic(path, viewToken = state.req?.viewToken)
* @param {((percent: number) => void) | undefined} [onupload]
* @param {Record<string, string | number>} [headers]
* @param {boolean} [isDir]
* @param {(() => void) | undefined} [onsent]
* @returns {Promise<any> & { xhr?: XMLHttpRequest | null, abort?: () => void }}
*/
export function postPublic(
+4
View File
@@ -638,6 +638,7 @@ export const mutations = {
state.user.fileLoading = {
maxConcurrentUpload: 3,
uploadChunkSizeMb: 5,
uploadStallTimeoutSeconds: 10,
downloadChunkSizeMb: 0,
clearAll: false
};
@@ -655,6 +656,9 @@ export const mutations = {
if (state.user.fileLoading.downloadChunkSizeMb === undefined) {
state.user.fileLoading.downloadChunkSizeMb = 0;
}
if (state.user.fileLoading.uploadStallTimeoutSeconds === undefined) {
state.user.fileLoading.uploadStallTimeoutSeconds = 10;
}
}
// Load stored values or use defaults as fallback
+1
View File
@@ -286,6 +286,7 @@ export interface UserObject {
fileLoading?: {
maxConcurrentUpload?: number;
uploadChunkSizeMb?: number;
uploadStallTimeoutSeconds?: number;
clearAll?: boolean;
downloadChunkSizeMb?: number;
maxConcurrent?: number;
+3 -3
View File
@@ -329,9 +329,9 @@ class UploadManager {
try {
const testPath = `${basePath}${dirName}`;
if (getters.isShare()) {
await resourcesApi.postPublic(state.shareInfo?.hash, testPath, new Blob([]), false, undefined, {}, true);
await resourcesApi.postPublic(state.shareInfo?.hash, testPath, new Blob([]), false, undefined, {}, true, undefined);
} else {
await resourcesApi.post(state.req?.source, testPath, new Blob([]), false, undefined, {}, true);
await resourcesApi.post(state.req?.source, testPath, new Blob([]), false, undefined, {}, true, undefined);
}
// No conflict - directory was created successfully
// Mark it so we can skip it later in the queue
@@ -839,7 +839,7 @@ class UploadManager {
}
progressTimeoutMs(): number {
const seconds = state.user?.fileLoading?.uploadStallTimeoutSeconds;
const seconds = state.user?.fileLoading?.uploadStallTimeoutSeconds ?? 0;
return seconds > 0 ? seconds * 1000 : this.PROGRESS_TIMEOUT_MS;
}