Files
rclone/backend/zoho/zoho_internal_test.go
T
Erol OzcanandNick Craig-Wood fb25801f35 zoho: preserve root_folder_id on reconnect and allow setting it
The Zoho config system ended every interactive create, update and
reconnect by calling m.Set("root_folder_id", workspaceID) in the
workspace_end state, with fs.ConfigChoose defaulting to the first
workspace. Any existing root_folder_id was therefore overwritten and
the remote silently repointed to the first workspace root.

This matters because reconnect is the documented fix for the 401
INVALID_OAUTHSCOPE download error - tokens issued before the
ZohoFiles.files.ALL scope was added lack download access - so users are
told to reconnect and then find all subsequent list/copy/sync/delete
operations pointed at a different, often shared, workspace.

Gate the workspace selection the way the drive backend does for team
drives (#5454): if a root_folder_id is already set, ask "Change current
root folder id ...?" defaulting to No and keep it; only run workspace
selection when it is empty or the user opts in. The token type rewrite
still runs on every reconnect so the scope refresh is unaffected. Also
expose root_folder_id as a standard advanced option (Sensitive, so
config redacted masks it) so it can be set and discovered like on
drive/box/onedrive.

Fixes #9575
2026-07-16 18:12:00 +01:00

72 lines
2.3 KiB
Go

package zoho
import (
"context"
"testing"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config/configmap"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestConfigRootFolderID drives the interactive Config state machine directly
// and checks that an existing root_folder_id is preserved on update/reconnect:
// workspace selection only runs when the id is empty or the user opts in. The
// seeded token is already the Zoho type and no region service is contacted, so
// these cases make no network calls.
func TestConfigRootFolderID(t *testing.T) {
regInfo := fs.MustFind("zoho")
const rootID = "abc123rootfolderid"
// Already the Zoho custom type, so the "type" state's token rewrite is a
// no-op and nothing is sent to the network.
const token = `{"access_token":"x","token_type":"Zoho-oauthtoken"}`
newMapper := func(withRoot bool) configmap.Simple {
m := configmap.Simple{"region": "eu", "token": token}
if withRoot {
m[configRootID] = rootID
}
return m
}
ctx := context.Background()
t.Run("SetRootAsksBeforeChanging", func(t *testing.T) {
m := newMapper(true)
out, err := regInfo.Config(ctx, "zoho", m, fs.ConfigIn{State: "type"})
require.NoError(t, err)
require.NotNil(t, out)
assert.Equal(t, "root_change", out.State)
require.NotNil(t, out.Option)
got, _ := m.Get(configRootID)
assert.Equal(t, rootID, got, "id must not change before the user answers")
})
t.Run("EmptyRootGoesToEdition", func(t *testing.T) {
m := newMapper(false)
out, err := regInfo.Config(ctx, "zoho", m, fs.ConfigIn{State: "type"})
require.NoError(t, err)
require.NotNil(t, out)
assert.Equal(t, "select_edition", out.State)
assert.Nil(t, out.Option, "goto, not a question")
})
t.Run("KeepRootOnNo", func(t *testing.T) {
m := newMapper(true)
out, err := regInfo.Config(ctx, "zoho", m, fs.ConfigIn{State: "root_change", Result: "false"})
require.NoError(t, err)
assert.Nil(t, out, "answering No ends config and keeps the id")
got, _ := m.Get(configRootID)
assert.Equal(t, rootID, got)
})
t.Run("ChangeRootOnYes", func(t *testing.T) {
m := newMapper(true)
out, err := regInfo.Config(ctx, "zoho", m, fs.ConfigIn{State: "root_change", Result: "true"})
require.NoError(t, err)
require.NotNil(t, out)
assert.Equal(t, "select_edition", out.State)
})
}