chore(coderd/workspaceapps/apptest): fix test flake due to concurrent usage of same deployment (#12700)

- Updates existing tests under workspaceapps/apptest to not reuse existing appDetails as assertWorkspaceLastUsed(Not)?Updated calls FlushStats() which was causing racy test behaviour and incorrect test assertions.
- Expands scope of assertWorkspaceLastUsedAtUpdated and its counterpart to ProxySubdomain tests.
This commit is contained in:
Cian Johnston 2024-03-21 15:38:38 +00:00 committed by GitHub
parent 5454f4997b
commit 8ea5fb7115
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 67 additions and 23 deletions

View File

@ -70,6 +70,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
t.Run("SignedTokenQueryParameter", func(t *testing.T) {
t.Parallel()
if appHostIsPrimary {
t.Skip("Tickets are not used for terminal requests on the primary.")
}
@ -101,8 +102,6 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
t.Run("WorkspaceAppsProxyPath", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, nil)
t.Run("Disabled", func(t *testing.T) {
t.Parallel()
@ -132,6 +131,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
t.Skip("This test only applies when testing apps on the primary.")
}
appDetails := setupProxyTest(t, nil)
unauthedClient := appDetails.AppClient(t)
unauthedClient.SetSessionToken("")
@ -148,7 +148,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
require.NoError(t, err)
require.True(t, loc.Query().Has("message"))
require.True(t, loc.Query().Has("redirect"))
assertWorkspaceLastUsedAtUpdated(t, appDetails)
assertWorkspaceLastUsedAtNotUpdated(t, appDetails)
})
t.Run("LoginWithoutAuthOnProxy", func(t *testing.T) {
@ -158,6 +158,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
t.Skip("This test only applies when testing apps on workspace proxies.")
}
appDetails := setupProxyTest(t, nil)
unauthedClient := appDetails.AppClient(t)
unauthedClient.SetSessionToken("")
@ -186,12 +187,13 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
// request is getting stripped.
require.Equal(t, u.Path, redirectURI.Path+"/")
require.Equal(t, u.RawQuery, redirectURI.RawQuery)
assertWorkspaceLastUsedAtUpdated(t, appDetails)
assertWorkspaceLastUsedAtNotUpdated(t, appDetails)
})
t.Run("NoAccessShould404", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, nil)
userClient, _ := coderdtest.CreateAnotherUser(t, appDetails.SDKClient, appDetails.FirstUser.OrganizationID, rbac.RoleMember())
userAppClient := appDetails.AppClient(t)
userAppClient.SetSessionToken(userClient.SessionToken())
@ -210,6 +212,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
t.Run("RedirectsWithSlash", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, nil)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
@ -226,6 +229,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
t.Run("RedirectsWithQuery", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, nil)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
@ -245,6 +249,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
t.Run("Proxies", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, nil)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
@ -333,6 +338,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
t.Run("BlocksMe", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, nil)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
@ -347,13 +353,13 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Contains(t, string(body), "must be accessed with the full username, not @me")
// TODO(cian): A blocked request should not count as workspace usage.
// assertWorkspaceLastUsedAtNotUpdated(t, appDetails.AppClient(t), appDetails)
assertWorkspaceLastUsedAtNotUpdated(t, appDetails)
})
t.Run("ForwardsIP", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, nil)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
@ -373,6 +379,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
t.Run("ProxyError", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, nil)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
@ -388,6 +395,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
t.Run("NoProxyPort", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, nil)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
@ -397,7 +405,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
// TODO(@deansheather): This should be 400. There's a todo in the
// resolve request code to fix this.
require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
assertWorkspaceLastUsedAtUpdated(t, appDetails)
assertWorkspaceLastUsedAtNotUpdated(t, appDetails)
})
})
@ -636,6 +644,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
_ = resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
require.Equal(t, resp.Header.Get("X-Got-Host"), u.Host)
assertWorkspaceLastUsedAtUpdated(t, appDetails)
})
t.Run("WorkspaceAppsProxySubdomainHostnamePrefix/Different", func(t *testing.T) {
@ -686,6 +695,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
require.NoError(t, err)
_ = resp.Body.Close()
require.NotEqual(t, http.StatusOK, resp.StatusCode)
assertWorkspaceLastUsedAtUpdated(t, appDetails)
})
// This test ensures that the subdomain handler does nothing if
@ -754,11 +764,10 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
t.Run("WorkspaceAppsProxySubdomain", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, nil)
t.Run("NoAccessShould401", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, nil)
userClient, _ := coderdtest.CreateAnotherUser(t, appDetails.SDKClient, appDetails.FirstUser.OrganizationID, rbac.RoleMember())
userAppClient := appDetails.AppClient(t)
userAppClient.SetSessionToken(userClient.SessionToken())
@ -770,11 +779,13 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusNotFound, resp.StatusCode)
assertWorkspaceLastUsedAtNotUpdated(t, appDetails)
})
t.Run("RedirectsWithSlash", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, nil)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
@ -789,11 +800,13 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
loc, err := resp.Location()
require.NoError(t, err)
require.Equal(t, appDetails.SubdomainAppURL(appDetails.Apps.Owner).Path, loc.Path)
assertWorkspaceLastUsedAtNotUpdated(t, appDetails)
})
t.Run("RedirectsWithQuery", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, nil)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
@ -807,11 +820,13 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
loc, err := resp.Location()
require.NoError(t, err)
require.Equal(t, appDetails.SubdomainAppURL(appDetails.Apps.Owner).RawQuery, loc.RawQuery)
assertWorkspaceLastUsedAtNotUpdated(t, appDetails)
})
t.Run("Proxies", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, nil)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
@ -848,6 +863,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
require.NoError(t, err)
require.Equal(t, proxyTestAppBody, string(body))
require.Equal(t, http.StatusOK, resp.StatusCode)
assertWorkspaceLastUsedAtUpdated(t, appDetails)
})
t.Run("ProxiesHTTPS", func(t *testing.T) {
@ -892,11 +908,13 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
require.NoError(t, err)
require.Equal(t, proxyTestAppBody, string(body))
require.Equal(t, http.StatusOK, resp.StatusCode)
assertWorkspaceLastUsedAtUpdated(t, appDetails)
})
t.Run("ProxiesPort", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, nil)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
@ -907,11 +925,13 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
require.NoError(t, err)
require.Equal(t, proxyTestAppBody, string(body))
require.Equal(t, http.StatusOK, resp.StatusCode)
assertWorkspaceLastUsedAtUpdated(t, appDetails)
})
t.Run("ProxyError", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, nil)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
@ -919,11 +939,13 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusBadGateway, resp.StatusCode)
assertWorkspaceLastUsedAtUpdated(t, appDetails)
})
t.Run("ProxyPortMinimumError", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, nil)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
@ -939,6 +961,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
err = json.NewDecoder(resp.Body).Decode(&resBody)
require.NoError(t, err)
require.Contains(t, resBody.Message, "Coder reserves ports less than")
assertWorkspaceLastUsedAtNotUpdated(t, appDetails)
})
t.Run("SuffixWildcardOK", func(t *testing.T) {
@ -961,6 +984,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
require.NoError(t, err)
require.Equal(t, proxyTestAppBody, string(body))
require.Equal(t, http.StatusOK, resp.StatusCode)
assertWorkspaceLastUsedAtUpdated(t, appDetails)
})
t.Run("WildcardPortOK", func(t *testing.T) {
@ -993,16 +1017,19 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
require.NoError(t, err)
require.Equal(t, proxyTestAppBody, string(body))
require.Equal(t, http.StatusOK, resp.StatusCode)
assertWorkspaceLastUsedAtUpdated(t, appDetails)
})
t.Run("SuffixWildcardNotMatch", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, &DeploymentOptions{
AppHost: "*-suffix.test.coder.com",
})
t.Run("NoSuffix", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, &DeploymentOptions{
AppHost: "*-suffix.test.coder.com",
})
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
@ -1020,11 +1047,16 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
// It's probably rendering the dashboard or a 404 page, so only
// ensure that the body doesn't match.
require.NotContains(t, string(body), proxyTestAppBody)
assertWorkspaceLastUsedAtNotUpdated(t, appDetails)
})
t.Run("DifferentSuffix", func(t *testing.T) {
t.Parallel()
appDetails := setupProxyTest(t, &DeploymentOptions{
AppHost: "*-suffix.test.coder.com",
})
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
@ -1042,6 +1074,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
// It's probably rendering the dashboard, so only ensure that the body
// doesn't match.
require.NotContains(t, string(body), proxyTestAppBody)
assertWorkspaceLastUsedAtNotUpdated(t, appDetails)
})
})
})
@ -1062,6 +1095,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusNotFound, resp.StatusCode)
assertWorkspaceLastUsedAtNotUpdated(t, appDetails)
})
t.Run("AuthenticatedOK", func(t *testing.T) {
@ -1090,6 +1124,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
assertWorkspaceLastUsedAtUpdated(t, appDetails)
})
t.Run("PublicOK", func(t *testing.T) {
@ -1117,6 +1152,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
assertWorkspaceLastUsedAtUpdated(t, appDetails)
})
t.Run("HTTPS", func(t *testing.T) {
@ -1145,6 +1181,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
assertWorkspaceLastUsedAtUpdated(t, appDetails)
})
})
@ -1719,26 +1756,33 @@ func testReconnectingPTY(ctx context.Context, t *testing.T, client *codersdk.Cli
}
// Accessing an app should update the workspace's LastUsedAt.
// NOTE: Despite our efforts with the flush channel, this is inherently racy.
// NOTE: Despite our efforts with the flush channel, this is inherently racy when used with
// parallel tests on the same workspace/app.
func assertWorkspaceLastUsedAtUpdated(t testing.TB, details *Details) {
t.Helper()
require.NotNil(t, details.Workspace, "can't assert LastUsedAt on a nil workspace!")
before, err := details.SDKClient.Workspace(context.Background(), details.Workspace.ID)
require.NoError(t, err)
// Wait for stats to fully flush.
details.FlushStats()
require.Eventually(t, func() bool {
details.FlushStats()
ws, err := details.SDKClient.Workspace(context.Background(), details.Workspace.ID)
assert.NoError(t, err)
return ws.LastUsedAt.After(details.Workspace.LastUsedAt)
}, testutil.WaitShort, testutil.IntervalMedium, "workspace LastUsedAt not updated when it should have been")
after, err := details.SDKClient.Workspace(context.Background(), details.Workspace.ID)
return assert.NoError(t, err) && after.LastUsedAt.After(before.LastUsedAt)
}, testutil.WaitShort, testutil.IntervalMedium)
}
// Except when it sometimes shouldn't (e.g. no access)
// NOTE: Despite our efforts with the flush channel, this is inherently racy.
// NOTE: Despite our efforts with the flush channel, this is inherently racy when used with
// parallel tests on the same workspace/app.
func assertWorkspaceLastUsedAtNotUpdated(t testing.TB, details *Details) {
t.Helper()
details.FlushStats()
ws, err := details.SDKClient.Workspace(context.Background(), details.Workspace.ID)
require.NotNil(t, details.Workspace, "can't assert LastUsedAt on a nil workspace!")
before, err := details.SDKClient.Workspace(context.Background(), details.Workspace.ID)
require.NoError(t, err)
require.Equal(t, ws.LastUsedAt, details.Workspace.LastUsedAt, "workspace LastUsedAt updated when it should not have been")
details.FlushStats()
after, err := details.SDKClient.Workspace(context.Background(), details.Workspace.ID)
require.NoError(t, err)
require.Equal(t, before.LastUsedAt, after.LastUsedAt, "workspace LastUsedAt updated when it should not have been")
}