Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions web/packages/shared/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Copyright 2023 Gravitational, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/*
* Be wary that this file is being loaded by both Node.js and browser environments, so keep the
* definitions simple and do not put environment-specific code here.
*
* If it turns out that the design package needs to depend on a constant from here, move this file
* to the design package or just its own package. This avoids cyclic dependencies as the shared
* package depends on the design package.
*/

/**
* TeleportNamespace is used as the namespace prefix for labels defined by Teleport which can
* carry metadata such as cloud AWS account or instance. Those labels can be used for RBAC.
*
* If a label with this prefix is used in a config file, the associated feature must take into
* account that the label might be removed, modified or could have been set by the user.
*
* See also teleport.internal and teleport.hidden.
*/
export const TeleportNamespace = 'teleport.dev' as const;

/**
* ConnectMyComputerNodeOwnerLabel is a label used to control access to the node managed by
* Teleport Connect as part of Connect My Computer.
*/
export const ConnectMyComputerNodeOwnerLabel =
`${TeleportNamespace}/connect-my-computer/owner` as const;
Original file line number Diff line number Diff line change
Expand Up @@ -15,41 +15,118 @@
*/

import React from 'react';
import { MemoryRouter } from 'react-router';
import { initialize, mswLoader } from 'msw-storybook-addon';
import { rest } from 'msw';

import {
OverrideUserAgent,
UserAgent,
} from 'shared/components/OverrideUserAgent';

import { ContextProvider } from 'teleport';
import cfg from 'teleport/config';
import { UserContext } from 'teleport/User/UserContext';
import { createTeleportContext } from 'teleport/mocks/contexts';
import { makeDefaultUserPreferences } from 'teleport/services/userPreferences/userPreferences';

import { SetupConnect } from './SetupConnect';

const oneDay = 1000 * 60 * 60 * 24;

const setupConnectProps = {
prevStep: () => {},
nextStep: () => {},
// Set high default intervals and timeouts so that stories don't poll for no reason.
pingInterval: oneDay,
showHintTimeout: oneDay,
};

initialize();

export default {
title: 'Teleport/Discover/ConnectMyComputer/SetupConnect',
loaders: [mswLoader],
};

const noNodesHandler = rest.get(cfg.api.nodesPath, (req, res, ctx) =>
res(ctx.json({ items: [] }))
);

export const macOS = () => (
<OverrideUserAgent userAgent={UserAgent.macOS}>
<Provider>
<SetupConnect prevStep={() => {}} />
<SetupConnect {...setupConnectProps} />
</Provider>
</OverrideUserAgent>
);

macOS.parameters = {
msw: {
handlers: [noNodesHandler],
},
};

export const Linux = () => (
<OverrideUserAgent userAgent={UserAgent.Linux}>
<Provider>
<SetupConnect prevStep={() => {}} />
<SetupConnect {...setupConnectProps} />
</Provider>
</OverrideUserAgent>
);

Linux.parameters = {
msw: {
handlers: [noNodesHandler],
},
};

export const Polling = () => (
<Provider>
<SetupConnect {...setupConnectProps} />
</Provider>
);

Polling.parameters = {
msw: {
handlers: [noNodesHandler],
},
};

export const PollingSuccess = () => (
<Provider>
<SetupConnect {...setupConnectProps} pingInterval={5} />
</Provider>
);

PollingSuccess.parameters = {
msw: {
handlers: [
rest.get(cfg.api.nodesPath, (req, res, ctx) => {
return res.once(ctx.json({ items: [] }));
}),
rest.get(cfg.api.nodesPath, (req, res, ctx) => {
return res(ctx.json({ items: [{ id: '1234', hostname: 'foo' }] }));
}),
],
},
};

export const HintTimeout = () => (
<Provider>
<SetupConnect {...setupConnectProps} showHintTimeout={1} />
</Provider>
);

HintTimeout.parameters = {
msw: {
handlers: [noNodesHandler],
},
};

const Provider = ({ children }) => {
const ctx = createTeleportContext();
// The proxy version is set mostly so that the download links point to actual artifacts.
ctx.storeUser.state.cluster.proxyVersion = '14.1.0';

const preferences = makeDefaultUserPreferences();
Expand All @@ -58,15 +135,17 @@ const Provider = ({ children }) => {
const updateClusterPinnedResources = () => Promise.resolve();

return (
<UserContext.Provider
value={{
preferences,
updatePreferences,
getClusterPinnedResources,
updateClusterPinnedResources,
}}
>
<ContextProvider ctx={ctx}>{children}</ContextProvider>
</UserContext.Provider>
<MemoryRouter>
<UserContext.Provider
value={{
preferences,
updatePreferences,
getClusterPinnedResources,
updateClusterPinnedResources,
}}
>
<ContextProvider ctx={ctx}>{children}</ContextProvider>
</UserContext.Provider>
</MemoryRouter>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* Copyright 2023 Gravitational, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { renderHook } from '@testing-library/react-hooks';

import * as useTeleport from 'teleport/useTeleport';
import NodeService from 'teleport/services/nodes/nodes';
import TeleportContext from 'teleport/teleportContext';

import { nodes } from 'teleport/Nodes/fixtures';

import { Node } from 'teleport/services/nodes';

import { usePollForConnectMyComputerNode } from './SetupConnect';

beforeEach(() => {
jest.restoreAllMocks();
});

describe('usePollForConnectMyComputerNode', () => {
const tests: Array<{
name: string;
initialNodes: Node[];
}> = [
{
name: 'returns the correct node if the first polling request returns no nodes',
initialNodes: [],
},
{
name: 'returns the correct node if the first polling request returns some nodes',
initialNodes: [nodes[1], nodes[2]],
},
];

test.each(tests)('$name', async ({ initialNodes }) => {
const expectedNode = nodes[0];

const nodeService = {
fetchNodes: jest.fn(),
} as Partial<NodeService> as NodeService;

jest
.mocked(nodeService)
.fetchNodes.mockResolvedValue({ agents: [...initialNodes, expectedNode] })
.mockResolvedValueOnce({ agents: initialNodes });

jest
.spyOn(useTeleport, 'default')
.mockReturnValue({ nodeService } as TeleportContext);

const { result, waitForValueToChange } = renderHook(() =>
usePollForConnectMyComputerNode({
username: 'alice',
clusterId: 'foo',
pingInterval: 1,
})
);

expect(result.error).toBeUndefined();
expect(result.current.node).toBeFalsy();
expect(result.current.isPolling).toBe(true);

await waitForValueToChange(() => result.current.node, { interval: 3 });

expect(result.current.node).toEqual(expectedNode);
expect(result.current.isPolling).toBe(false);
});
});
Loading