mirror of
https://github.com/graphql-hive/console
synced 2026-04-21 14:37:17 +00:00
run integration tests locally against local running services (#5096)
This commit is contained in:
parent
bfeddbf6e8
commit
1ca63a758b
53 changed files with 1638 additions and 1484 deletions
14
.vscode/terminals.json
vendored
14
.vscode/terminals.json
vendored
|
|
@ -92,6 +92,20 @@
|
|||
"open": true,
|
||||
"cwd": "packages/services/usage-ingestor",
|
||||
"command": "pnpm dev"
|
||||
},
|
||||
{
|
||||
"name": "external-composition:dev",
|
||||
"description": "Run External Composition example",
|
||||
"open": true,
|
||||
"cwd": "packages/libraries/external-composition/",
|
||||
"command": "pnpm example"
|
||||
},
|
||||
{
|
||||
"name": "external-composition-fed2:dev",
|
||||
"description": "Run External Composition for Federation 2",
|
||||
"open": true,
|
||||
"cwd": "packages/services/external-composition/federation-2",
|
||||
"command": "pnpm dev"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ services:
|
|||
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server:24.4-alpine
|
||||
mem_limit: 300m
|
||||
mem_limit: 500m
|
||||
environment:
|
||||
CLICKHOUSE_USER: test
|
||||
CLICKHOUSE_PASSWORD: test
|
||||
|
|
|
|||
|
|
@ -16,41 +16,31 @@ We are using Vitest to test the following concerns:
|
|||
|
||||
Integration tests are based pre-built Docker images, so you can run it in 2 modes:
|
||||
|
||||
#### Running from Source Code
|
||||
#### Running from built artifact (Docker)
|
||||
|
||||
**TL;DR**: Use `pnpm integration:prepare` command to set up the complete environment from locally
|
||||
running integration tests. You can ignore the rest of the commands in this section, if this script
|
||||
worked for you, and just run `pnpm test:integration` to run the actual tests.
|
||||
**To run integration tests locally with a built artifact, you need to build a Docker image for the
|
||||
services. **
|
||||
|
||||
To run integration tests locally, from the local source code, you need to build a valid Docker
|
||||
image.
|
||||
Use `pnpm integration:prepare` command to set up the complete environment from locally running
|
||||
integration tests.
|
||||
|
||||
To do so, follow these instructions:
|
||||
And then, to run the tests, use the following:
|
||||
|
||||
1. Install all deps: `pnpm i`
|
||||
2. Generate types: `pnpm graphql:generate`
|
||||
3. Build source code: `pnpm build`
|
||||
4. Set env vars:
|
||||
```bash
|
||||
export COMMIT_SHA="local"
|
||||
export RELEASE="local"
|
||||
export BRANCH_NAME="local"
|
||||
export BUILD_TYPE=""
|
||||
export DOCKER_TAG=":local"
|
||||
```
|
||||
5. Compile a local Docker image by running:
|
||||
`docker buildx bake -f docker/docker.hcl integration-tests --load`
|
||||
6. Use Docker Compose to run the built containers (based on `community` compose file), along with
|
||||
the extra containers:
|
||||
```
|
||||
pnpm test:integration
|
||||
```
|
||||
|
||||
```bash
|
||||
export DOCKER_TAG=":local"
|
||||
export DOCKER_REGISTRY=""
|
||||
|
||||
docker compose -f ./docker/docker-compose.community.yml -f ./integration-tests/docker-compose.integration.yaml --env-file ./integration-tests/.env up -d --wait
|
||||
```
|
||||
> Make sure to run the prepare command every time you change your code.
|
||||
|
||||
7. Run the tests: `pnpm --filter integration-tests test:integration`
|
||||
#### Running from source code (local services)
|
||||
|
||||
You can also run integration tests against the local-running services, during development.
|
||||
|
||||
To do so, use the following instructions:
|
||||
|
||||
1. Run Hive from VSCode (using `Start Hive` button).
|
||||
2. Make sure all services are running correctly.
|
||||
3. Run integration tests with `pnpm dev:integration` command (from root directory).
|
||||
|
||||
#### Running from Pre-Built Docker Image
|
||||
|
||||
|
|
|
|||
25
integration-tests/local-dev.ts
Normal file
25
integration-tests/local-dev.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { readFileSync } from 'fs';
|
||||
import { parse } from 'dotenv';
|
||||
|
||||
function applyEnv(env: Record<string, string>) {
|
||||
for (const key in env) {
|
||||
process.env[key] = env[key];
|
||||
}
|
||||
}
|
||||
|
||||
const serverEnvVars = parse(readFileSync('../packages/services/server/.env', 'utf-8'));
|
||||
|
||||
applyEnv({
|
||||
SUPERTOKENS_CONNECTION_URI: serverEnvVars.SUPERTOKENS_CONNECTION_URI,
|
||||
SUPERTOKENS_API_KEY: serverEnvVars.SUPERTOKENS_API_KEY,
|
||||
POSTGRES_USER: serverEnvVars.POSTGRES_USER,
|
||||
POSTGRES_PASSWORD: serverEnvVars.POSTGRES_PASSWORD,
|
||||
POSTGRES_DB: serverEnvVars.POSTGRES_DB,
|
||||
POSTGRES_PORT: serverEnvVars.POSTGRES_PORT,
|
||||
POSTGRES_HOST: serverEnvVars.POSTGRES_HOST,
|
||||
HIVE_APP_BASE_URL: serverEnvVars.WEB_APP_URL,
|
||||
EXTERNAL_COMPOSITION_SECRET: 'secretsecret',
|
||||
CLICKHOUSE_USER: serverEnvVars.CLICKHOUSE_USERNAME,
|
||||
CLICKHOUSE_PASSWORD: serverEnvVars.CLICKHOUSE_PASSWORD,
|
||||
HIVE_ENCRYPTION_SECRET: serverEnvVars.HIVE_ENCRYPTION_SECRET,
|
||||
});
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev:integration": "RUN_AGAINST_LOCAL_SERVICES=1 vitest",
|
||||
"prepare:env": "cd ../ && pnpm build:libraries && pnpm build:services",
|
||||
"test:integration": "vitest",
|
||||
"typecheck": "tsc --noEmit"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
import { getServiceHost } from './utils';
|
||||
|
||||
export const serviceName = 'external_composition';
|
||||
export const servicePort = 3012;
|
||||
|
||||
export async function history(): Promise<string[]> {
|
||||
const dockerAddress = await getServiceHost(serviceName, servicePort);
|
||||
const dockerAddress = await getServiceHost('external_composition', 3012);
|
||||
const res = await fetch(`http://${dockerAddress}/_history`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,44 @@ function getDockerConnection() {
|
|||
return docker;
|
||||
}
|
||||
|
||||
export async function getServiceHost(serviceName: string, servicePort: number): Promise<string> {
|
||||
const LOCAL_SERVICES = {
|
||||
server: 3001,
|
||||
clickhouse: 8123,
|
||||
emails: 6260,
|
||||
composition_federation_2: 3069,
|
||||
usage: 4001,
|
||||
schema: 6500,
|
||||
external_composition: 3012,
|
||||
} as const;
|
||||
|
||||
export type KnownServices = keyof typeof LOCAL_SERVICES;
|
||||
|
||||
export function getCDNPort() {
|
||||
if (process.env.RUN_AGAINST_LOCAL_SERVICES === '1') {
|
||||
return 9000;
|
||||
}
|
||||
|
||||
return 8083;
|
||||
}
|
||||
|
||||
export function getAppBaseUrl() {
|
||||
if (process.env.RUN_AGAINST_LOCAL_SERVICES === '1') {
|
||||
return 'localhost:3000';
|
||||
}
|
||||
|
||||
return 'localhost:8080';
|
||||
}
|
||||
|
||||
export async function getServiceHost(
|
||||
serviceName: KnownServices,
|
||||
servicePort: number,
|
||||
localhost = true,
|
||||
): Promise<string> {
|
||||
if (process.env.RUN_AGAINST_LOCAL_SERVICES === '1') {
|
||||
return `localhost:${LOCAL_SERVICES[serviceName]}`;
|
||||
}
|
||||
|
||||
const actualHost = localhost ? 'localhost' : serviceName;
|
||||
const docker = getDockerConnection();
|
||||
const containers = await docker.listContainers({
|
||||
filters: JSON.stringify({
|
||||
|
|
@ -42,7 +79,7 @@ export async function getServiceHost(serviceName: string, servicePort: number):
|
|||
}
|
||||
|
||||
if (publicPort) {
|
||||
return `localhost:${publicPort.PublicPort}`;
|
||||
return `${actualHost}:${publicPort.PublicPort}`;
|
||||
}
|
||||
|
||||
if (privatePort) {
|
||||
|
|
@ -50,10 +87,10 @@ export async function getServiceHost(serviceName: string, servicePort: number):
|
|||
`Container "${container.Id}" (service: "${serviceName}") expose port "${servicePort}" as "${privatePort.PublicPort}", please consider to update your setup!`,
|
||||
);
|
||||
|
||||
return `localhost:${privatePort.PublicPort}`;
|
||||
return `${actualHost}:${privatePort.PublicPort}`;
|
||||
}
|
||||
|
||||
return `localhost:${servicePort}`;
|
||||
return `${actualHost}:${servicePort}`;
|
||||
}
|
||||
|
||||
export function generateUnique() {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { createSupergraphManager } from '@graphql-hive/apollo';
|
|||
import { graphql } from '../../testkit/gql';
|
||||
import { execute } from '../../testkit/graphql';
|
||||
import { initSeed } from '../../testkit/seed';
|
||||
import { getServiceHost } from '../../testkit/utils';
|
||||
import { getCDNPort, getServiceHost, KnownServices } from '../../testkit/utils';
|
||||
|
||||
const s3Client = new S3Client({
|
||||
endpoint: 'http://127.0.0.1:9000',
|
||||
|
|
@ -85,7 +85,7 @@ function generateLegacyToken(targetId: string) {
|
|||
*/
|
||||
function runArtifactsCDNTests(
|
||||
name: string,
|
||||
runtime: { service: string; port: number; path: string },
|
||||
runtime: { service: KnownServices; port: number; path: string },
|
||||
) {
|
||||
const getBaseEndpoint = () =>
|
||||
getServiceHost(runtime.service, runtime.port).then(v => `http://${v}${runtime.path}`);
|
||||
|
|
@ -313,7 +313,7 @@ function runArtifactsCDNTests(
|
|||
const locationUrl = new URL(locationHeader!);
|
||||
expect(locationUrl.protocol).toBe('http:');
|
||||
expect(locationUrl.hostname).toBe('localhost');
|
||||
expect(locationUrl.port).toBe('8083');
|
||||
expect(locationUrl.port).toBe(getCDNPort().toString());
|
||||
|
||||
response = await fetch(locationHeader!, {
|
||||
method: 'GET',
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ const CreateOIDCIntegrationMutation = graphql(`
|
|||
|
||||
describe('create', () => {
|
||||
describe('permissions="organization:integrations"', () => {
|
||||
test.concurrent('success', async () => {
|
||||
test.concurrent('success', async ({ expect }) => {
|
||||
const { ownerToken, createOrg } = await initSeed().createOwner();
|
||||
const { organization } = await createOrg();
|
||||
|
||||
|
|
@ -315,7 +315,7 @@ describe('create', () => {
|
|||
`);
|
||||
});
|
||||
|
||||
test.concurrent('error: multiple integrations per organization', async () => {
|
||||
test.concurrent('error: multiple integrations per organization', async ({ expect }) => {
|
||||
const { ownerToken, createOrg } = await initSeed().createOwner();
|
||||
const { organization } = await createOrg();
|
||||
|
||||
|
|
@ -399,7 +399,7 @@ const DeleteOIDCIntegrationMutation = graphql(`
|
|||
|
||||
describe('delete', () => {
|
||||
describe('permissions="organization:integrations"', () => {
|
||||
test.concurrent('success', async () => {
|
||||
test.concurrent('success', async ({ expect }) => {
|
||||
const { ownerToken, createOrg } = await initSeed().createOwner();
|
||||
const { organization } = await createOrg();
|
||||
|
||||
|
|
@ -621,7 +621,7 @@ const UpdateOIDCIntegrationMutation = graphql(`
|
|||
|
||||
describe('update', () => {
|
||||
describe('permissions="organization:integrations"', () => {
|
||||
test.concurrent('success', async () => {
|
||||
test.concurrent('success', async ({ expect }) => {
|
||||
const { ownerToken, createOrg } = await initSeed().createOwner();
|
||||
const { organization } = await createOrg();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';
|
|||
import { changeOrganizationSlug, renameOrganization } from '../../../testkit/flow';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
|
||||
test.concurrent('renaming an organization should NOT affect its cleanId', async () => {
|
||||
test.concurrent('renaming an organization should NOT affect its cleanId', async ({ expect }) => {
|
||||
const { ownerToken, createOrg } = await initSeed().createOwner();
|
||||
const { organization } = await createOrg();
|
||||
|
||||
|
|
@ -22,28 +22,31 @@ test.concurrent('renaming an organization should NOT affect its cleanId', async
|
|||
expect(result?.selector.organization).toEqual(organization.cleanId);
|
||||
});
|
||||
|
||||
test.concurrent('renaming an organization to the same name should be possible', async () => {
|
||||
const { ownerToken, createOrg } = await initSeed().createOwner();
|
||||
const { organization } = await createOrg();
|
||||
test.concurrent(
|
||||
'renaming an organization to the same name should be possible',
|
||||
async ({ expect }) => {
|
||||
const { ownerToken, createOrg } = await initSeed().createOwner();
|
||||
const { organization } = await createOrg();
|
||||
|
||||
const renamedOrganizationResult = await renameOrganization(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
name: organization.name,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
const renamedOrganizationResult = await renameOrganization(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
name: organization.name,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
const result = renamedOrganizationResult.updateOrganizationName.ok?.updatedOrganizationPayload;
|
||||
expect(renamedOrganizationResult.updateOrganizationName.error).toBeNull();
|
||||
expect(result?.organization.name).toBe(organization.name);
|
||||
expect(result?.organization.cleanId).toEqual(organization.cleanId);
|
||||
expect(result?.selector.organization).toEqual(organization.cleanId);
|
||||
});
|
||||
const result = renamedOrganizationResult.updateOrganizationName.ok?.updatedOrganizationPayload;
|
||||
expect(renamedOrganizationResult.updateOrganizationName.error).toBeNull();
|
||||
expect(result?.organization.name).toBe(organization.name);
|
||||
expect(result?.organization.cleanId).toEqual(organization.cleanId);
|
||||
expect(result?.selector.organization).toEqual(organization.cleanId);
|
||||
},
|
||||
);
|
||||
|
||||
test.concurrent(
|
||||
'modifying a clean id of an organization to the same value should not change the clean id',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { ownerToken, createOrg } = await initSeed().createOwner();
|
||||
const { organization } = await createOrg();
|
||||
|
||||
|
|
@ -64,29 +67,33 @@ test.concurrent(
|
|||
},
|
||||
);
|
||||
|
||||
test.concurrent('modifying a clean id of an organization should be possible', async () => {
|
||||
const { ownerToken, createOrg } = await initSeed().createOwner();
|
||||
const { organization } = await createOrg();
|
||||
test.concurrent(
|
||||
'modifying a clean id of an organization should be possible',
|
||||
async ({ expect }) => {
|
||||
const { ownerToken, createOrg } = await initSeed().createOwner();
|
||||
const { organization } = await createOrg();
|
||||
|
||||
const newCleanId = randomUUID();
|
||||
const changeOrganizationSlugResult = await changeOrganizationSlug(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
slug: newCleanId,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
const newCleanId = randomUUID();
|
||||
const changeOrganizationSlugResult = await changeOrganizationSlug(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
slug: newCleanId,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
const result = changeOrganizationSlugResult.updateOrganizationSlug.ok?.updatedOrganizationPayload;
|
||||
expect(changeOrganizationSlugResult.updateOrganizationSlug.error).toBeNull();
|
||||
expect(result?.organization.name).toBe(organization.name);
|
||||
expect(result?.organization.cleanId).toEqual(newCleanId);
|
||||
expect(result?.selector.organization).toEqual(newCleanId);
|
||||
});
|
||||
const result =
|
||||
changeOrganizationSlugResult.updateOrganizationSlug.ok?.updatedOrganizationPayload;
|
||||
expect(changeOrganizationSlugResult.updateOrganizationSlug.error).toBeNull();
|
||||
expect(result?.organization.name).toBe(organization.name);
|
||||
expect(result?.organization.cleanId).toEqual(newCleanId);
|
||||
expect(result?.selector.organization).toEqual(newCleanId);
|
||||
},
|
||||
);
|
||||
|
||||
test.concurrent(
|
||||
'modifying a clean id of an organization to a reserved keyword should fail',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { ownerToken, createOrg } = await initSeed().createOwner();
|
||||
const { organization } = await createOrg();
|
||||
|
||||
|
|
@ -106,7 +113,7 @@ test.concurrent(
|
|||
|
||||
test.concurrent(
|
||||
'modifying a clean id of an organization to a taken clean id should fail',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { ownerToken, createOrg } = await initSeed().createOwner();
|
||||
const { organization } = await createOrg();
|
||||
const { organization: anotherOrganization } = await createOrg();
|
||||
|
|
|
|||
|
|
@ -7,111 +7,117 @@ import {
|
|||
import { waitFor } from '../../../testkit/flow';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
|
||||
test.concurrent('freshly created organization has Get Started progress at 0%', async () => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { fetchOrganizationInfo } = await createOrg();
|
||||
const { getStarted: steps1 } = await fetchOrganizationInfo();
|
||||
test.concurrent(
|
||||
'freshly created organization has Get Started progress at 0%',
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { fetchOrganizationInfo } = await createOrg();
|
||||
const { getStarted: steps1 } = await fetchOrganizationInfo();
|
||||
|
||||
expect(steps1?.creatingProject).toBe(false);
|
||||
expect(steps1?.publishingSchema).toBe(false);
|
||||
expect(steps1?.checkingSchema).toBe(false);
|
||||
expect(steps1?.invitingMembers).toBe(false);
|
||||
expect(steps1?.reportingOperations).toBe(false);
|
||||
expect(steps1?.enablingUsageBasedBreakingChanges).toBe(false);
|
||||
});
|
||||
expect(steps1?.creatingProject).toBe(false);
|
||||
expect(steps1?.publishingSchema).toBe(false);
|
||||
expect(steps1?.checkingSchema).toBe(false);
|
||||
expect(steps1?.invitingMembers).toBe(false);
|
||||
expect(steps1?.reportingOperations).toBe(false);
|
||||
expect(steps1?.enablingUsageBasedBreakingChanges).toBe(false);
|
||||
},
|
||||
);
|
||||
|
||||
test.concurrent('completing each step should result in updated Get Started progress', async () => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { inviteAndJoinMember, fetchOrganizationInfo, createProject } = await createOrg();
|
||||
const { target, project, createToken } = await createProject(ProjectType.Single);
|
||||
test.concurrent(
|
||||
'completing each step should result in updated Get Started progress',
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { inviteAndJoinMember, fetchOrganizationInfo, createProject } = await createOrg();
|
||||
const { target, project, createToken } = await createProject(ProjectType.Single);
|
||||
|
||||
const { getStarted: steps } = await fetchOrganizationInfo();
|
||||
expect(steps?.creatingProject).toBe(true); // modified
|
||||
expect(steps?.publishingSchema).toBe(false);
|
||||
expect(steps?.checkingSchema).toBe(false);
|
||||
expect(steps?.invitingMembers).toBe(false);
|
||||
expect(steps?.reportingOperations).toBe(false);
|
||||
expect(steps?.enablingUsageBasedBreakingChanges).toBe(false);
|
||||
const { getStarted: steps } = await fetchOrganizationInfo();
|
||||
expect(steps?.creatingProject).toBe(true); // modified
|
||||
expect(steps?.publishingSchema).toBe(false);
|
||||
expect(steps?.checkingSchema).toBe(false);
|
||||
expect(steps?.invitingMembers).toBe(false);
|
||||
expect(steps?.reportingOperations).toBe(false);
|
||||
expect(steps?.enablingUsageBasedBreakingChanges).toBe(false);
|
||||
|
||||
if (!target || !project) {
|
||||
throw new Error('Failed to create project');
|
||||
}
|
||||
if (!target || !project) {
|
||||
throw new Error('Failed to create project');
|
||||
}
|
||||
|
||||
const {
|
||||
publishSchema,
|
||||
checkSchema,
|
||||
collectLegacyOperations: collectOperations,
|
||||
toggleTargetValidation,
|
||||
} = await createToken({
|
||||
targetScopes: [
|
||||
TargetAccessScope.Read,
|
||||
TargetAccessScope.RegistryRead,
|
||||
TargetAccessScope.RegistryWrite,
|
||||
TargetAccessScope.Settings,
|
||||
],
|
||||
projectScopes: [ProjectAccessScope.Read],
|
||||
organizationScopes: [OrganizationAccessScope.Read],
|
||||
});
|
||||
const {
|
||||
publishSchema,
|
||||
checkSchema,
|
||||
collectLegacyOperations: collectOperations,
|
||||
toggleTargetValidation,
|
||||
} = await createToken({
|
||||
targetScopes: [
|
||||
TargetAccessScope.Read,
|
||||
TargetAccessScope.RegistryRead,
|
||||
TargetAccessScope.RegistryWrite,
|
||||
TargetAccessScope.Settings,
|
||||
],
|
||||
projectScopes: [ProjectAccessScope.Read],
|
||||
organizationScopes: [OrganizationAccessScope.Read],
|
||||
});
|
||||
|
||||
// Step: publish schema
|
||||
await publishSchema({ sdl: 'type Query { foo: String }' }).then(r => r.expectNoGraphQLErrors());
|
||||
const { getStarted: steps2 } = await fetchOrganizationInfo();
|
||||
expect(steps2?.creatingProject).toBe(true);
|
||||
expect(steps2?.publishingSchema).toBe(true); // modified
|
||||
expect(steps2?.checkingSchema).toBe(false);
|
||||
expect(steps2?.invitingMembers).toBe(false);
|
||||
expect(steps2?.reportingOperations).toBe(false);
|
||||
expect(steps2?.enablingUsageBasedBreakingChanges).toBe(false);
|
||||
// Step: publish schema
|
||||
await publishSchema({ sdl: 'type Query { foo: String }' }).then(r => r.expectNoGraphQLErrors());
|
||||
const { getStarted: steps2 } = await fetchOrganizationInfo();
|
||||
expect(steps2?.creatingProject).toBe(true);
|
||||
expect(steps2?.publishingSchema).toBe(true); // modified
|
||||
expect(steps2?.checkingSchema).toBe(false);
|
||||
expect(steps2?.invitingMembers).toBe(false);
|
||||
expect(steps2?.reportingOperations).toBe(false);
|
||||
expect(steps2?.enablingUsageBasedBreakingChanges).toBe(false);
|
||||
|
||||
// Step: checking schema
|
||||
await checkSchema('type Query { foo: String bar: String }');
|
||||
const { getStarted: steps3 } = await fetchOrganizationInfo();
|
||||
expect(steps3?.creatingProject).toBe(true);
|
||||
expect(steps3?.publishingSchema).toBe(true);
|
||||
expect(steps3?.checkingSchema).toBe(true); // modified
|
||||
expect(steps3?.invitingMembers).toBe(false);
|
||||
expect(steps3?.reportingOperations).toBe(false);
|
||||
expect(steps3?.enablingUsageBasedBreakingChanges).toBe(false);
|
||||
// Step: checking schema
|
||||
await checkSchema('type Query { foo: String bar: String }');
|
||||
const { getStarted: steps3 } = await fetchOrganizationInfo();
|
||||
expect(steps3?.creatingProject).toBe(true);
|
||||
expect(steps3?.publishingSchema).toBe(true);
|
||||
expect(steps3?.checkingSchema).toBe(true); // modified
|
||||
expect(steps3?.invitingMembers).toBe(false);
|
||||
expect(steps3?.reportingOperations).toBe(false);
|
||||
expect(steps3?.enablingUsageBasedBreakingChanges).toBe(false);
|
||||
|
||||
// Step: inviting members
|
||||
await inviteAndJoinMember();
|
||||
const { getStarted: steps4 } = await fetchOrganizationInfo();
|
||||
expect(steps4?.creatingProject).toBe(true);
|
||||
expect(steps4?.publishingSchema).toBe(true);
|
||||
expect(steps4?.checkingSchema).toBe(true);
|
||||
expect(steps4?.invitingMembers).toBe(true); // modified
|
||||
expect(steps4?.reportingOperations).toBe(false);
|
||||
expect(steps4?.enablingUsageBasedBreakingChanges).toBe(false);
|
||||
// Step: inviting members
|
||||
await inviteAndJoinMember();
|
||||
const { getStarted: steps4 } = await fetchOrganizationInfo();
|
||||
expect(steps4?.creatingProject).toBe(true);
|
||||
expect(steps4?.publishingSchema).toBe(true);
|
||||
expect(steps4?.checkingSchema).toBe(true);
|
||||
expect(steps4?.invitingMembers).toBe(true); // modified
|
||||
expect(steps4?.reportingOperations).toBe(false);
|
||||
expect(steps4?.enablingUsageBasedBreakingChanges).toBe(false);
|
||||
|
||||
// Step: reporting operations
|
||||
await collectOperations([
|
||||
{
|
||||
operationName: 'foo',
|
||||
operation: 'query foo { foo }',
|
||||
fields: ['Query', 'Query.foo'],
|
||||
execution: {
|
||||
duration: 2_000_000,
|
||||
ok: true,
|
||||
errorsTotal: 0,
|
||||
// Step: reporting operations
|
||||
await collectOperations([
|
||||
{
|
||||
operationName: 'foo',
|
||||
operation: 'query foo { foo }',
|
||||
fields: ['Query', 'Query.foo'],
|
||||
execution: {
|
||||
duration: 2_000_000,
|
||||
ok: true,
|
||||
errorsTotal: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
await waitFor(5000);
|
||||
const { getStarted: steps5 } = await fetchOrganizationInfo();
|
||||
expect(steps5?.creatingProject).toBe(true);
|
||||
expect(steps5?.publishingSchema).toBe(true);
|
||||
expect(steps5?.checkingSchema).toBe(true);
|
||||
expect(steps5?.invitingMembers).toBe(true);
|
||||
expect(steps5?.reportingOperations).toBe(true); // modified
|
||||
expect(steps5?.enablingUsageBasedBreakingChanges).toBe(false);
|
||||
]);
|
||||
await waitFor(8000);
|
||||
const { getStarted: steps5 } = await fetchOrganizationInfo();
|
||||
expect(steps5?.creatingProject).toBe(true);
|
||||
expect(steps5?.publishingSchema).toBe(true);
|
||||
expect(steps5?.checkingSchema).toBe(true);
|
||||
expect(steps5?.invitingMembers).toBe(true);
|
||||
expect(steps5?.reportingOperations).toBe(true); // modified
|
||||
expect(steps5?.enablingUsageBasedBreakingChanges).toBe(false);
|
||||
|
||||
// Step: target validation
|
||||
await toggleTargetValidation(true);
|
||||
const { getStarted: steps6 } = await fetchOrganizationInfo();
|
||||
expect(steps6?.creatingProject).toBe(true);
|
||||
expect(steps6?.publishingSchema).toBe(true);
|
||||
expect(steps6?.checkingSchema).toBe(true);
|
||||
expect(steps6?.invitingMembers).toBe(true);
|
||||
expect(steps6?.reportingOperations).toBe(true);
|
||||
expect(steps6?.enablingUsageBasedBreakingChanges).toBe(true); // modified
|
||||
});
|
||||
// Step: target validation
|
||||
await toggleTargetValidation(true);
|
||||
const { getStarted: steps6 } = await fetchOrganizationInfo();
|
||||
expect(steps6?.creatingProject).toBe(true);
|
||||
expect(steps6?.publishingSchema).toBe(true);
|
||||
expect(steps6?.checkingSchema).toBe(true);
|
||||
expect(steps6?.invitingMembers).toBe(true);
|
||||
expect(steps6?.reportingOperations).toBe(true);
|
||||
expect(steps6?.enablingUsageBasedBreakingChanges).toBe(true); // modified
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
import { history } from '../../../testkit/emails';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
|
||||
test.concurrent('owner of an organization should have all scopes', async () => {
|
||||
test.concurrent('owner of an organization should have all scopes', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { organization } = await createOrg();
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ test.concurrent('owner of an organization should have all scopes', async () => {
|
|||
});
|
||||
});
|
||||
|
||||
test.concurrent('invited member should have basic scopes (Viewer role)', async () => {
|
||||
test.concurrent('invited member should have basic scopes (Viewer role)', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { inviteAndJoinMember } = await createOrg();
|
||||
const { member } = await inviteAndJoinMember();
|
||||
|
|
@ -46,7 +46,7 @@ test.concurrent('invited member should have basic scopes (Viewer role)', async (
|
|||
|
||||
test.concurrent(
|
||||
'cannot create a role with an access scope that user has no access to',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { inviteAndJoinMember, organization } = await createOrg();
|
||||
const { createMemberRole, assignMemberRole, member } = await inviteAndJoinMember();
|
||||
|
|
@ -80,7 +80,7 @@ test.concurrent(
|
|||
|
||||
test.concurrent(
|
||||
'cannot grant an access scope to another user if user has no access to that scope',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { inviteAndJoinMember, organization } = await createOrg();
|
||||
const { createMemberRole, assignMemberRole, member } = await inviteAndJoinMember();
|
||||
|
|
@ -120,7 +120,7 @@ test.concurrent(
|
|||
|
||||
test.concurrent(
|
||||
'granting no scopes is equal to setting read-only for org, project and target',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { inviteAndJoinMember } = await createOrg();
|
||||
const { createMemberRole } = await inviteAndJoinMember();
|
||||
|
|
@ -139,7 +139,7 @@ test.concurrent(
|
|||
},
|
||||
);
|
||||
|
||||
test.concurrent('cannot downgrade a member when assigning a new role', async () => {
|
||||
test.concurrent('cannot downgrade a member when assigning a new role', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { inviteAndJoinMember } = await createOrg();
|
||||
const { createMemberRole, assignMemberRole, member } = await inviteAndJoinMember();
|
||||
|
|
@ -196,7 +196,7 @@ test.concurrent('cannot downgrade a member when assigning a new role', async ()
|
|||
});
|
||||
});
|
||||
|
||||
test.concurrent('cannot downgrade a member when modifying a role', async () => {
|
||||
test.concurrent('cannot downgrade a member when modifying a role', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { inviteAndJoinMember } = await createOrg();
|
||||
const { createMemberRole, assignMemberRole, member, updateMemberRole } =
|
||||
|
|
@ -252,7 +252,7 @@ test.concurrent('cannot downgrade a member when modifying a role', async () => {
|
|||
});
|
||||
});
|
||||
|
||||
test.concurrent('cannot delete a role with members', async () => {
|
||||
test.concurrent('cannot delete a role with members', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { inviteAndJoinMember } = await createOrg();
|
||||
const { createMemberRole, deleteMemberRole, assignMemberRole, member } =
|
||||
|
|
@ -290,7 +290,7 @@ test.concurrent('cannot delete a role with members', async () => {
|
|||
).rejects.toThrowError('Cannot delete a role with members');
|
||||
});
|
||||
|
||||
test.concurrent('cannot invite a member with more access than the inviter', async () => {
|
||||
test.concurrent('cannot invite a member with more access than the inviter', async ({ expect }) => {
|
||||
const seed = initSeed();
|
||||
const { createOrg } = await seed.createOwner();
|
||||
const { inviteMember, inviteAndJoinMember, organization } = await createOrg();
|
||||
|
|
@ -334,7 +334,7 @@ test.concurrent('cannot invite a member with more access than the inviter', asyn
|
|||
expect(inviteCode).toBeDefined();
|
||||
});
|
||||
|
||||
test.concurrent('email invitation', async () => {
|
||||
test.concurrent('email invitation', async ({ expect }) => {
|
||||
const seed = initSeed();
|
||||
const { createOrg } = await seed.createOwner();
|
||||
const { inviteMember } = await createOrg();
|
||||
|
|
@ -348,33 +348,36 @@ test.concurrent('email invitation', async () => {
|
|||
expect(sentEmails).toContainEqual(expect.objectContaining({ to: inviteEmail }));
|
||||
});
|
||||
|
||||
test.concurrent('cannot join organization twice using the same invitation code', async () => {
|
||||
const seed = initSeed();
|
||||
const { createOrg } = await seed.createOwner();
|
||||
const { inviteMember, joinMemberUsingCode } = await createOrg();
|
||||
test.concurrent(
|
||||
'cannot join organization twice using the same invitation code',
|
||||
async ({ expect }) => {
|
||||
const seed = initSeed();
|
||||
const { createOrg } = await seed.createOwner();
|
||||
const { inviteMember, joinMemberUsingCode } = await createOrg();
|
||||
|
||||
// Invite
|
||||
const invitationResult = await inviteMember();
|
||||
const inviteCode = invitationResult.ok!.code;
|
||||
expect(inviteCode).toBeDefined();
|
||||
// Invite
|
||||
const invitationResult = await inviteMember();
|
||||
const inviteCode = invitationResult.ok!.code;
|
||||
expect(inviteCode).toBeDefined();
|
||||
|
||||
// Join
|
||||
const extra = seed.generateEmail();
|
||||
const { access_token: member_access_token } = await seed.authenticate(extra);
|
||||
const joinResult = await (
|
||||
await joinMemberUsingCode(inviteCode, member_access_token)
|
||||
).expectNoGraphQLErrors();
|
||||
// Join
|
||||
const extra = seed.generateEmail();
|
||||
const { access_token: member_access_token } = await seed.authenticate(extra);
|
||||
const joinResult = await (
|
||||
await joinMemberUsingCode(inviteCode, member_access_token)
|
||||
).expectNoGraphQLErrors();
|
||||
|
||||
expect(joinResult.joinOrganization.__typename).toBe('OrganizationPayload');
|
||||
expect(joinResult.joinOrganization.__typename).toBe('OrganizationPayload');
|
||||
|
||||
if (joinResult.joinOrganization.__typename !== 'OrganizationPayload') {
|
||||
throw new Error('Join failed');
|
||||
}
|
||||
if (joinResult.joinOrganization.__typename !== 'OrganizationPayload') {
|
||||
throw new Error('Join failed');
|
||||
}
|
||||
|
||||
const other = seed.generateEmail();
|
||||
const { access_token: other_access_token } = await seed.authenticate(other);
|
||||
const otherJoinResult = await (
|
||||
await joinMemberUsingCode(inviteCode, other_access_token)
|
||||
).expectNoGraphQLErrors();
|
||||
expect(otherJoinResult.joinOrganization.__typename).toBe('OrganizationInvitationError');
|
||||
});
|
||||
const other = seed.generateEmail();
|
||||
const { access_token: other_access_token } = await seed.authenticate(other);
|
||||
const otherJoinResult = await (
|
||||
await joinMemberUsingCode(inviteCode, other_access_token)
|
||||
).expectNoGraphQLErrors();
|
||||
expect(otherJoinResult.joinOrganization.__typename).toBe('OrganizationInvitationError');
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { initSeed } from '../../../testkit/seed';
|
|||
|
||||
test.concurrent(
|
||||
'accessing non-existing ownership transfer request should result in null',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { organization } = await createOrg();
|
||||
|
||||
|
|
@ -28,43 +28,49 @@ test.concurrent(
|
|||
},
|
||||
);
|
||||
|
||||
test.concurrent('owner should be able to request the ownership transfer to a member', async () => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember } = await createOrg();
|
||||
const { member, memberEmail } = await inviteAndJoinMember();
|
||||
test.concurrent(
|
||||
'owner should be able to request the ownership transfer to a member',
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember } = await createOrg();
|
||||
const { member, memberEmail } = await inviteAndJoinMember();
|
||||
|
||||
const transferRequestResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
const transferRequestResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
expect(transferRequestResult.requestOrganizationTransfer.ok?.email).toBe(memberEmail);
|
||||
});
|
||||
expect(transferRequestResult.requestOrganizationTransfer.ok?.email).toBe(memberEmail);
|
||||
},
|
||||
);
|
||||
|
||||
test.concurrent('non-owner should not be able to request the ownership transfer', async () => {
|
||||
const { createOrg, ownerEmail } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember, members } = await createOrg();
|
||||
const { memberToken } = await inviteAndJoinMember();
|
||||
const orgMembers = await members();
|
||||
test.concurrent(
|
||||
'non-owner should not be able to request the ownership transfer',
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerEmail } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember, members } = await createOrg();
|
||||
const { memberToken } = await inviteAndJoinMember();
|
||||
const orgMembers = await members();
|
||||
|
||||
const errors = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: orgMembers.find(u => u.user.email === ownerEmail)!.user.id,
|
||||
},
|
||||
memberToken,
|
||||
).then(r => r.expectGraphQLErrors());
|
||||
const errors = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: orgMembers.find(u => u.user.email === ownerEmail)!.user.id,
|
||||
},
|
||||
memberToken,
|
||||
).then(r => r.expectGraphQLErrors());
|
||||
|
||||
expect(errors).toBeDefined();
|
||||
expect(errors.length).toBe(1);
|
||||
});
|
||||
expect(errors).toBeDefined();
|
||||
expect(errors.length).toBe(1);
|
||||
},
|
||||
);
|
||||
|
||||
test.concurrent(
|
||||
'owner should not be able to request the ownership transfer to non-member',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember } = await createOrg();
|
||||
const { memberToken, member } = await inviteAndJoinMember();
|
||||
|
|
@ -81,71 +87,77 @@ test.concurrent(
|
|||
},
|
||||
);
|
||||
|
||||
test.concurrent('non-member should not be able to access the transfer request', async () => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember } = await createOrg();
|
||||
const { member } = await inviteAndJoinMember();
|
||||
test.concurrent(
|
||||
'non-member should not be able to access the transfer request',
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember } = await createOrg();
|
||||
const { member } = await inviteAndJoinMember();
|
||||
|
||||
const requestTransferResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
const requestTransferResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
const code = requestTransferResult.requestOrganizationTransfer.ok?.code;
|
||||
const code = requestTransferResult.requestOrganizationTransfer.ok?.code;
|
||||
|
||||
if (!code) {
|
||||
throw new Error('Could not create transfer request');
|
||||
}
|
||||
if (!code) {
|
||||
throw new Error('Could not create transfer request');
|
||||
}
|
||||
|
||||
const { ownerToken: nonMemberToken } = await initSeed().createOwner();
|
||||
const { ownerToken: nonMemberToken } = await initSeed().createOwner();
|
||||
|
||||
const errors = await getOrganizationTransferRequest(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
code,
|
||||
},
|
||||
nonMemberToken,
|
||||
).then(r => r.expectGraphQLErrors());
|
||||
const errors = await getOrganizationTransferRequest(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
code,
|
||||
},
|
||||
nonMemberToken,
|
||||
).then(r => r.expectGraphQLErrors());
|
||||
|
||||
expect(errors).toBeDefined();
|
||||
expect(errors.length).toBe(1);
|
||||
});
|
||||
expect(errors).toBeDefined();
|
||||
expect(errors.length).toBe(1);
|
||||
},
|
||||
);
|
||||
|
||||
test.concurrent('non-recipient should not be able to access the transfer request', async () => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember } = await createOrg();
|
||||
const { member } = await inviteAndJoinMember();
|
||||
const { memberToken: lonelyMemberToken } = await inviteAndJoinMember();
|
||||
test.concurrent(
|
||||
'non-recipient should not be able to access the transfer request',
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember } = await createOrg();
|
||||
const { member } = await inviteAndJoinMember();
|
||||
const { memberToken: lonelyMemberToken } = await inviteAndJoinMember();
|
||||
|
||||
const requestTransferResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
const requestTransferResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
const code = requestTransferResult.requestOrganizationTransfer.ok?.code;
|
||||
const code = requestTransferResult.requestOrganizationTransfer.ok?.code;
|
||||
|
||||
if (!code) {
|
||||
throw new Error('Could not create transfer request');
|
||||
}
|
||||
if (!code) {
|
||||
throw new Error('Could not create transfer request');
|
||||
}
|
||||
|
||||
const requestResult = await getOrganizationTransferRequest(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
code,
|
||||
},
|
||||
lonelyMemberToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
const requestResult = await getOrganizationTransferRequest(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
code,
|
||||
},
|
||||
lonelyMemberToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
expect(requestResult.organizationTransferRequest).toBeNull();
|
||||
});
|
||||
expect(requestResult.organizationTransferRequest).toBeNull();
|
||||
},
|
||||
);
|
||||
|
||||
test.concurrent('recipient should be able to access the transfer request', async () => {
|
||||
test.concurrent('recipient should be able to access the transfer request', async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember } = await createOrg();
|
||||
const { member, memberToken } = await inviteAndJoinMember();
|
||||
|
|
@ -174,7 +186,7 @@ test.concurrent('recipient should be able to access the transfer request', async
|
|||
expect(requestResult.organizationTransferRequest).not.toBeNull();
|
||||
});
|
||||
|
||||
test.concurrent('recipient should be able to answer the ownership transfer', async () => {
|
||||
test.concurrent('recipient should be able to answer the ownership transfer', async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember } = await createOrg();
|
||||
const { member, memberToken } = await inviteAndJoinMember();
|
||||
|
|
@ -205,39 +217,42 @@ test.concurrent('recipient should be able to answer the ownership transfer', asy
|
|||
expect(answerResult.answerOrganizationTransferRequest.ok?.accepted).toBe(true);
|
||||
});
|
||||
|
||||
test.concurrent('non-member should not be able to answer the ownership transfer', async () => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember } = await createOrg();
|
||||
const { member } = await inviteAndJoinMember();
|
||||
const { memberToken: lonelyMemberToken } = await inviteAndJoinMember();
|
||||
test.concurrent(
|
||||
'non-member should not be able to answer the ownership transfer',
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember } = await createOrg();
|
||||
const { member } = await inviteAndJoinMember();
|
||||
const { memberToken: lonelyMemberToken } = await inviteAndJoinMember();
|
||||
|
||||
const requestTransferResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
const requestTransferResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
const code = requestTransferResult.requestOrganizationTransfer.ok?.code;
|
||||
const code = requestTransferResult.requestOrganizationTransfer.ok?.code;
|
||||
|
||||
if (!code) {
|
||||
throw new Error('Could not create transfer request');
|
||||
}
|
||||
if (!code) {
|
||||
throw new Error('Could not create transfer request');
|
||||
}
|
||||
|
||||
const answerResult = await answerOrganizationTransferRequest(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
code,
|
||||
accept: true,
|
||||
},
|
||||
lonelyMemberToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
const answerResult = await answerOrganizationTransferRequest(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
code,
|
||||
accept: true,
|
||||
},
|
||||
lonelyMemberToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
expect(answerResult.answerOrganizationTransferRequest.error?.message).toBeDefined();
|
||||
});
|
||||
expect(answerResult.answerOrganizationTransferRequest.error?.message).toBeDefined();
|
||||
},
|
||||
);
|
||||
|
||||
test.concurrent('owner should not be able to answer the ownership transfer', async () => {
|
||||
test.concurrent('owner should not be able to answer the ownership transfer', async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember } = await createOrg();
|
||||
const { member } = await inviteAndJoinMember();
|
||||
|
|
@ -268,41 +283,44 @@ test.concurrent('owner should not be able to answer the ownership transfer', asy
|
|||
expect(answerResult.answerOrganizationTransferRequest.error?.message).toBeDefined();
|
||||
});
|
||||
|
||||
test.concurrent('non-member should not be able to answer the ownership transfer', async () => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember } = await createOrg();
|
||||
const { member } = await inviteAndJoinMember();
|
||||
|
||||
const requestTransferResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
const code = requestTransferResult.requestOrganizationTransfer.ok?.code;
|
||||
|
||||
if (!code) {
|
||||
throw new Error('Could not create transfer request');
|
||||
}
|
||||
|
||||
const { ownerToken: nonMemberToken } = await initSeed().createOwner();
|
||||
const answerResult = await answerOrganizationTransferRequest(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
code,
|
||||
accept: true,
|
||||
},
|
||||
nonMemberToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
expect(answerResult.answerOrganizationTransferRequest.error?.message).toBeDefined();
|
||||
});
|
||||
|
||||
test.concurrent(
|
||||
'non-member should not be able to answer the ownership transfer',
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember } = await createOrg();
|
||||
const { member } = await inviteAndJoinMember();
|
||||
|
||||
const requestTransferResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
const code = requestTransferResult.requestOrganizationTransfer.ok?.code;
|
||||
|
||||
if (!code) {
|
||||
throw new Error('Could not create transfer request');
|
||||
}
|
||||
|
||||
const { ownerToken: nonMemberToken } = await initSeed().createOwner();
|
||||
const answerResult = await answerOrganizationTransferRequest(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
code,
|
||||
accept: true,
|
||||
},
|
||||
nonMemberToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
expect(answerResult.answerOrganizationTransferRequest.error?.message).toBeDefined();
|
||||
},
|
||||
);
|
||||
|
||||
test.concurrent(
|
||||
'previous owner should keep the ownership until the new owner accepts the transfer',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken, ownerEmail } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember, members } = await createOrg();
|
||||
const { member } = await inviteAndJoinMember();
|
||||
|
|
@ -350,7 +368,7 @@ test.concurrent(
|
|||
|
||||
test.concurrent(
|
||||
'previous owner should have an Admin role, new owner should get an Admin role as well',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken, ownerEmail } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember, members } = await createOrg();
|
||||
const { member, memberToken } = await inviteAndJoinMember();
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ describe('Policy Access', () => {
|
|||
|
||||
test.concurrent(
|
||||
'should successfully fetch Target.schemaPolicy if the user has access to SETTINGS',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { organization, createProject, inviteAndJoinMember } = await createOrg();
|
||||
const { project, target } = await createProject(ProjectType.Single);
|
||||
|
|
@ -53,7 +53,7 @@ describe('Policy Access', () => {
|
|||
|
||||
test.concurrent(
|
||||
'should fail to fetch Target.schemaPolicy if the user lacks access to SETTINGS',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { organization, createProject, inviteAndJoinMember } = await createOrg();
|
||||
const { project, target } = await createProject(ProjectType.Single);
|
||||
|
|
@ -87,7 +87,7 @@ describe('Policy Access', () => {
|
|||
|
||||
test.concurrent(
|
||||
'should successfully fetch Project.schemaPolicy if the user has access to SETTINGS',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { organization, createProject, inviteAndJoinMember } = await createOrg();
|
||||
const { project } = await createProject(ProjectType.Single);
|
||||
|
|
@ -120,7 +120,7 @@ describe('Policy Access', () => {
|
|||
|
||||
test.concurrent(
|
||||
'should fail to fetch Project.schemaPolicy if the user lacks access to SETTINGS',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { organization, createProject, inviteAndJoinMember } = await createOrg();
|
||||
const { project, target } = await createProject(ProjectType.Single);
|
||||
|
|
@ -154,7 +154,7 @@ describe('Policy Access', () => {
|
|||
`);
|
||||
test.concurrent(
|
||||
'should successfully fetch Organization.schemaPolicy if the user has access to SETTINGS',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember } = await createOrg();
|
||||
const adminRole = organization.memberRoles.find(r => r.name === 'Admin');
|
||||
|
|
@ -185,7 +185,7 @@ describe('Policy Access', () => {
|
|||
|
||||
test.concurrent(
|
||||
'should fail to fetch Organization.schemaPolicy if the user lacks access to SETTINGS',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { organization, inviteAndJoinMember } = await createOrg();
|
||||
const { memberToken } = await inviteAndJoinMember();
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ describe('Policy CRUD', () => {
|
|||
describe('Target level', () => {
|
||||
test.concurrent(
|
||||
'Should return empty policy when project and org does not have one',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { organization, createProject } = await createOrg();
|
||||
const { project, target } = await createProject(ProjectType.Single);
|
||||
|
|
@ -233,7 +233,7 @@ describe('Policy CRUD', () => {
|
|||
describe('Project level', () => {
|
||||
test.concurrent(
|
||||
'creating a project should NOT create a record in the database for the policy',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { organization, createProject } = await createOrg();
|
||||
await createProject(ProjectType.Single);
|
||||
|
|
@ -329,7 +329,7 @@ describe('Policy CRUD', () => {
|
|||
describe('Org level', () => {
|
||||
test.concurrent(
|
||||
'creating a org should NOT create a record in the database for the policy',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { organization, createProject } = await createOrg();
|
||||
await createProject(ProjectType.Single);
|
||||
|
|
@ -347,7 +347,7 @@ describe('Policy CRUD', () => {
|
|||
},
|
||||
);
|
||||
|
||||
test.concurrent('invalid rule name is rejected with an error', async () => {
|
||||
test.concurrent('invalid rule name is rejected with an error', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, setOrganizationSchemaPolicy } = await createOrg();
|
||||
await createProject(ProjectType.Single);
|
||||
|
|
@ -358,7 +358,7 @@ describe('Policy CRUD', () => {
|
|||
expect(upsertResult.error?.message).toContain('Unknown rule name passed');
|
||||
});
|
||||
|
||||
test.concurrent('invalid rule config is rejected with an error', async () => {
|
||||
test.concurrent('invalid rule config is rejected with an error', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, setOrganizationSchemaPolicy } = await createOrg();
|
||||
await createProject(ProjectType.Single);
|
||||
|
|
@ -371,7 +371,7 @@ describe('Policy CRUD', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test.concurrent('empty rule config is rejected with an error', async () => {
|
||||
test.concurrent('empty rule config is rejected with an error', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, setOrganizationSchemaPolicy } = await createOrg();
|
||||
await createProject(ProjectType.Single);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { initSeed } from '../../../testkit/seed';
|
|||
|
||||
test.concurrent(
|
||||
'creating a project should result in creating the development, staging and production targets',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { targets } = await createProject(ProjectType.Single);
|
||||
|
|
@ -31,7 +31,7 @@ test.concurrent(
|
|||
},
|
||||
);
|
||||
|
||||
test.concurrent('renaming a project should result changing its cleanId', async () => {
|
||||
test.concurrent('renaming a project should result changing its cleanId', async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { project } = await createProject(ProjectType.Single);
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ test('rate limit approaching and reached for organization', async () => {
|
|||
const collectResult = await collectOperations(new Array(10).fill(op));
|
||||
expect(collectResult.status).toEqual(200);
|
||||
|
||||
await waitFor(5000);
|
||||
await waitFor(8000);
|
||||
|
||||
let sent = await emails.history();
|
||||
expect(sent).toContainEqual({
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { execute } from '../../../testkit/graphql';
|
|||
import { initSeed } from '../../../testkit/seed';
|
||||
import { createPolicy } from '../policy/policy-check.spec';
|
||||
|
||||
test.concurrent('can check a schema with target:registry:read access', async () => {
|
||||
test.concurrent('can check a schema with target:registry:read access', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(ProjectType.Single);
|
||||
|
|
@ -65,7 +65,7 @@ test.concurrent('can check a schema with target:registry:read access', async ()
|
|||
expect(checkResultValid.schemaCheck.__typename).toBe('SchemaCheckSuccess');
|
||||
});
|
||||
|
||||
test.concurrent('should match indentation of previous description', async () => {
|
||||
test.concurrent('should match indentation of previous description', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(ProjectType.Single);
|
||||
|
|
@ -244,152 +244,158 @@ const ApproveFailedSchemaCheckMutation = graphql(/* GraphQL */ `
|
|||
}
|
||||
`);
|
||||
|
||||
test.concurrent('successful check without previously published schema is persisted', async () => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, target } = await createProject(ProjectType.Single);
|
||||
test.concurrent(
|
||||
'successful check without previously published schema is persisted',
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, target } = await createProject(ProjectType.Single);
|
||||
|
||||
// Create a token with read rights
|
||||
const readToken = await createToken({
|
||||
targetScopes: [TargetAccessScope.RegistryRead],
|
||||
projectScopes: [],
|
||||
organizationScopes: [],
|
||||
});
|
||||
// Create a token with read rights
|
||||
const readToken = await createToken({
|
||||
targetScopes: [TargetAccessScope.RegistryRead],
|
||||
projectScopes: [],
|
||||
organizationScopes: [],
|
||||
});
|
||||
|
||||
// Check schema with read rights
|
||||
const checkResult = await readToken
|
||||
.checkSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
ping: String
|
||||
pong: String
|
||||
}
|
||||
`)
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
const check = checkResult.schemaCheck;
|
||||
|
||||
if (check.__typename !== 'SchemaCheckSuccess') {
|
||||
throw new Error(`Expected SchemaCheckSuccess, got ${check.__typename}`);
|
||||
}
|
||||
|
||||
const schemaCheckId = check.schemaCheck?.id;
|
||||
|
||||
if (schemaCheckId == null) {
|
||||
throw new Error('Missing schema check id.');
|
||||
}
|
||||
|
||||
const schemaCheck = await execute({
|
||||
document: SchemaCheckQuery,
|
||||
variables: {
|
||||
selector: {
|
||||
organization: organization.cleanId,
|
||||
project: project.cleanId,
|
||||
target: target.cleanId,
|
||||
},
|
||||
id: schemaCheckId,
|
||||
},
|
||||
authToken: readToken.secret,
|
||||
}).then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
expect(schemaCheck).toMatchObject({
|
||||
target: {
|
||||
schemaCheck: {
|
||||
__typename: 'SuccessfulSchemaCheck',
|
||||
id: schemaCheckId,
|
||||
createdAt: expect.any(String),
|
||||
serviceName: null,
|
||||
schemaVersion: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('successful check with previously published schema is persisted', async () => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, target } = await createProject(ProjectType.Single);
|
||||
|
||||
// Create a token with write rights
|
||||
const writeToken = await createToken({
|
||||
targetScopes: [
|
||||
TargetAccessScope.Read,
|
||||
TargetAccessScope.RegistryRead,
|
||||
TargetAccessScope.RegistryWrite,
|
||||
TargetAccessScope.Settings,
|
||||
],
|
||||
});
|
||||
|
||||
// Publish schema with write rights
|
||||
const publishResult = await writeToken
|
||||
.publishSchema({
|
||||
sdl: /* GraphQL */ `
|
||||
// Check schema with read rights
|
||||
const checkResult = await readToken
|
||||
.checkSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
ping: String
|
||||
pong: String
|
||||
}
|
||||
`,
|
||||
})
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
`)
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
const check = checkResult.schemaCheck;
|
||||
|
||||
// Schema publish should be successful
|
||||
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
||||
if (check.__typename !== 'SchemaCheckSuccess') {
|
||||
throw new Error(`Expected SchemaCheckSuccess, got ${check.__typename}`);
|
||||
}
|
||||
|
||||
// Create a token with read rights
|
||||
const readToken = await createToken({
|
||||
targetScopes: [TargetAccessScope.RegistryRead],
|
||||
projectScopes: [],
|
||||
organizationScopes: [],
|
||||
});
|
||||
const schemaCheckId = check.schemaCheck?.id;
|
||||
|
||||
// Check schema with read rights
|
||||
const checkResult = await readToken
|
||||
.checkSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
ping: String
|
||||
pong: String
|
||||
}
|
||||
`)
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
const check = checkResult.schemaCheck;
|
||||
if (schemaCheckId == null) {
|
||||
throw new Error('Missing schema check id.');
|
||||
}
|
||||
|
||||
if (check.__typename !== 'SchemaCheckSuccess') {
|
||||
throw new Error(`Expected SchemaCheckSuccess, got ${check.__typename}`);
|
||||
}
|
||||
|
||||
const schemaCheckId = check.schemaCheck?.id;
|
||||
|
||||
if (schemaCheckId == null) {
|
||||
throw new Error('Missing schema check id.');
|
||||
}
|
||||
|
||||
const schemaCheck = await execute({
|
||||
document: SchemaCheckQuery,
|
||||
variables: {
|
||||
selector: {
|
||||
organization: organization.cleanId,
|
||||
project: project.cleanId,
|
||||
target: target.cleanId,
|
||||
},
|
||||
id: schemaCheckId,
|
||||
},
|
||||
authToken: readToken.secret,
|
||||
}).then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
expect(schemaCheck).toMatchObject({
|
||||
target: {
|
||||
schemaCheck: {
|
||||
__typename: 'SuccessfulSchemaCheck',
|
||||
const schemaCheck = await execute({
|
||||
document: SchemaCheckQuery,
|
||||
variables: {
|
||||
selector: {
|
||||
organization: organization.cleanId,
|
||||
project: project.cleanId,
|
||||
target: target.cleanId,
|
||||
},
|
||||
id: schemaCheckId,
|
||||
createdAt: expect.any(String),
|
||||
serviceName: null,
|
||||
schemaVersion: {
|
||||
id: expect.any(String),
|
||||
},
|
||||
authToken: readToken.secret,
|
||||
}).then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
expect(schemaCheck).toMatchObject({
|
||||
target: {
|
||||
schemaCheck: {
|
||||
__typename: 'SuccessfulSchemaCheck',
|
||||
id: schemaCheckId,
|
||||
createdAt: expect.any(String),
|
||||
serviceName: null,
|
||||
schemaVersion: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.concurrent('failed check due to graphql validation is persisted', async () => {
|
||||
test.concurrent(
|
||||
'successful check with previously published schema is persisted',
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, target } = await createProject(ProjectType.Single);
|
||||
|
||||
// Create a token with write rights
|
||||
const writeToken = await createToken({
|
||||
targetScopes: [
|
||||
TargetAccessScope.Read,
|
||||
TargetAccessScope.RegistryRead,
|
||||
TargetAccessScope.RegistryWrite,
|
||||
TargetAccessScope.Settings,
|
||||
],
|
||||
});
|
||||
|
||||
// Publish schema with write rights
|
||||
const publishResult = await writeToken
|
||||
.publishSchema({
|
||||
sdl: /* GraphQL */ `
|
||||
type Query {
|
||||
ping: String
|
||||
pong: String
|
||||
}
|
||||
`,
|
||||
})
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
// Schema publish should be successful
|
||||
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
||||
|
||||
// Create a token with read rights
|
||||
const readToken = await createToken({
|
||||
targetScopes: [TargetAccessScope.RegistryRead],
|
||||
projectScopes: [],
|
||||
organizationScopes: [],
|
||||
});
|
||||
|
||||
// Check schema with read rights
|
||||
const checkResult = await readToken
|
||||
.checkSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
ping: String
|
||||
pong: String
|
||||
}
|
||||
`)
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
const check = checkResult.schemaCheck;
|
||||
|
||||
if (check.__typename !== 'SchemaCheckSuccess') {
|
||||
throw new Error(`Expected SchemaCheckSuccess, got ${check.__typename}`);
|
||||
}
|
||||
|
||||
const schemaCheckId = check.schemaCheck?.id;
|
||||
|
||||
if (schemaCheckId == null) {
|
||||
throw new Error('Missing schema check id.');
|
||||
}
|
||||
|
||||
const schemaCheck = await execute({
|
||||
document: SchemaCheckQuery,
|
||||
variables: {
|
||||
selector: {
|
||||
organization: organization.cleanId,
|
||||
project: project.cleanId,
|
||||
target: target.cleanId,
|
||||
},
|
||||
id: schemaCheckId,
|
||||
},
|
||||
authToken: readToken.secret,
|
||||
}).then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
expect(schemaCheck).toMatchObject({
|
||||
target: {
|
||||
schemaCheck: {
|
||||
__typename: 'SuccessfulSchemaCheck',
|
||||
id: schemaCheckId,
|
||||
createdAt: expect.any(String),
|
||||
serviceName: null,
|
||||
schemaVersion: {
|
||||
id: expect.any(String),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.concurrent('failed check due to graphql validation is persisted', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, target } = await createProject(ProjectType.Single);
|
||||
|
|
@ -454,7 +460,7 @@ test.concurrent('failed check due to graphql validation is persisted', async ()
|
|||
});
|
||||
});
|
||||
|
||||
test.concurrent('failed check due to breaking change is persisted', async () => {
|
||||
test.concurrent('failed check due to breaking change is persisted', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, target } = await createProject(ProjectType.Single);
|
||||
|
|
@ -546,7 +552,7 @@ test.concurrent('failed check due to breaking change is persisted', async () =>
|
|||
});
|
||||
});
|
||||
|
||||
test.concurrent('failed check due to policy error is persisted', async () => {
|
||||
test.concurrent('failed check due to policy error is persisted', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, target, setProjectSchemaPolicy } = await createProject(
|
||||
|
|
@ -655,123 +661,126 @@ test.concurrent('failed check due to policy error is persisted', async () => {
|
|||
});
|
||||
});
|
||||
|
||||
test.concurrent('successful check with warnings and safe changes is persisted', async () => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, target, setProjectSchemaPolicy } = await createProject(
|
||||
ProjectType.Single,
|
||||
);
|
||||
test.concurrent(
|
||||
'successful check with warnings and safe changes is persisted',
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, target, setProjectSchemaPolicy } = await createProject(
|
||||
ProjectType.Single,
|
||||
);
|
||||
|
||||
await setProjectSchemaPolicy(createPolicy(RuleInstanceSeverityLevel.Warning));
|
||||
await setProjectSchemaPolicy(createPolicy(RuleInstanceSeverityLevel.Warning));
|
||||
|
||||
// Create a token with write rights
|
||||
const writeToken = await createToken({
|
||||
targetScopes: [
|
||||
TargetAccessScope.Read,
|
||||
TargetAccessScope.RegistryRead,
|
||||
TargetAccessScope.RegistryWrite,
|
||||
TargetAccessScope.Settings,
|
||||
],
|
||||
});
|
||||
// Create a token with write rights
|
||||
const writeToken = await createToken({
|
||||
targetScopes: [
|
||||
TargetAccessScope.Read,
|
||||
TargetAccessScope.RegistryRead,
|
||||
TargetAccessScope.RegistryWrite,
|
||||
TargetAccessScope.Settings,
|
||||
],
|
||||
});
|
||||
|
||||
// Publish schema with write rights
|
||||
const publishResult = await writeToken
|
||||
.publishSchema({
|
||||
sdl: /* GraphQL */ `
|
||||
// Publish schema with write rights
|
||||
const publishResult = await writeToken
|
||||
.publishSchema({
|
||||
sdl: /* GraphQL */ `
|
||||
type Query {
|
||||
ping: String
|
||||
}
|
||||
`,
|
||||
})
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
// Schema publish should be successful
|
||||
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
||||
|
||||
// Create a token with read rights
|
||||
const readToken = await createToken({
|
||||
targetScopes: [TargetAccessScope.RegistryRead],
|
||||
projectScopes: [],
|
||||
organizationScopes: [],
|
||||
});
|
||||
|
||||
// Check schema with read rights
|
||||
const checkResult = await readToken
|
||||
.checkSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
ping: String
|
||||
foo: String
|
||||
}
|
||||
`,
|
||||
})
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
`)
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
const check = checkResult.schemaCheck;
|
||||
|
||||
// Schema publish should be successful
|
||||
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
||||
if (check.__typename !== 'SchemaCheckSuccess') {
|
||||
throw new Error(`Expected SchemaCheckSuccess, got ${check.__typename}`);
|
||||
}
|
||||
|
||||
// Create a token with read rights
|
||||
const readToken = await createToken({
|
||||
targetScopes: [TargetAccessScope.RegistryRead],
|
||||
projectScopes: [],
|
||||
organizationScopes: [],
|
||||
});
|
||||
const schemaCheckId = check.schemaCheck?.id;
|
||||
|
||||
// Check schema with read rights
|
||||
const checkResult = await readToken
|
||||
.checkSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
ping: String
|
||||
foo: String
|
||||
}
|
||||
`)
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
const check = checkResult.schemaCheck;
|
||||
if (schemaCheckId == null) {
|
||||
throw new Error('Missing schema check id.');
|
||||
}
|
||||
|
||||
if (check.__typename !== 'SchemaCheckSuccess') {
|
||||
throw new Error(`Expected SchemaCheckSuccess, got ${check.__typename}`);
|
||||
}
|
||||
|
||||
const schemaCheckId = check.schemaCheck?.id;
|
||||
|
||||
if (schemaCheckId == null) {
|
||||
throw new Error('Missing schema check id.');
|
||||
}
|
||||
|
||||
const schemaCheck = await execute({
|
||||
document: SchemaCheckQuery,
|
||||
variables: {
|
||||
selector: {
|
||||
organization: organization.cleanId,
|
||||
project: project.cleanId,
|
||||
target: target.cleanId,
|
||||
},
|
||||
id: schemaCheckId,
|
||||
},
|
||||
authToken: readToken.secret,
|
||||
}).then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
expect(schemaCheck).toMatchObject({
|
||||
target: {
|
||||
schemaCheck: {
|
||||
__typename: 'SuccessfulSchemaCheck',
|
||||
id: schemaCheckId,
|
||||
createdAt: expect.any(String),
|
||||
serviceName: null,
|
||||
schemaVersion: {
|
||||
id: expect.any(String),
|
||||
const schemaCheck = await execute({
|
||||
document: SchemaCheckQuery,
|
||||
variables: {
|
||||
selector: {
|
||||
organization: organization.cleanId,
|
||||
project: project.cleanId,
|
||||
target: target.cleanId,
|
||||
},
|
||||
schemaPolicyWarnings: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
end: {
|
||||
column: 17,
|
||||
line: 2,
|
||||
},
|
||||
message: 'Description is required for type "Query"',
|
||||
ruleId: 'require-description',
|
||||
start: {
|
||||
column: 12,
|
||||
line: 2,
|
||||
id: schemaCheckId,
|
||||
},
|
||||
authToken: readToken.secret,
|
||||
}).then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
expect(schemaCheck).toMatchObject({
|
||||
target: {
|
||||
schemaCheck: {
|
||||
__typename: 'SuccessfulSchemaCheck',
|
||||
id: schemaCheckId,
|
||||
createdAt: expect.any(String),
|
||||
serviceName: null,
|
||||
schemaVersion: {
|
||||
id: expect.any(String),
|
||||
},
|
||||
schemaPolicyWarnings: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
end: {
|
||||
column: expect.any(Number),
|
||||
line: expect.any(Number),
|
||||
},
|
||||
message: 'Description is required for type "Query"',
|
||||
ruleId: 'require-description',
|
||||
start: {
|
||||
column: expect.any(Number),
|
||||
line: expect.any(Number),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
safeSchemaChanges: {
|
||||
nodes: [
|
||||
{
|
||||
message: "Field 'foo' was added to object type 'Query'",
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
safeSchemaChanges: {
|
||||
nodes: [
|
||||
{
|
||||
message: "Field 'foo' was added to object type 'Query'",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.concurrent(
|
||||
'failed check due to missing service name is not persisted (federation/stitching)',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(ProjectType.Federation);
|
||||
|
|
@ -801,7 +810,7 @@ test.concurrent(
|
|||
},
|
||||
);
|
||||
|
||||
test.concurrent('metadata is persisted', async () => {
|
||||
test.concurrent('metadata is persisted', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, target } = await createProject(ProjectType.Single);
|
||||
|
|
@ -873,7 +882,7 @@ test.concurrent('metadata is persisted', async () => {
|
|||
|
||||
test.concurrent(
|
||||
'approve failed schema check that has breaking change status to successful and attaches meta information to the breaking change',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, target } = await createProject(ProjectType.Single);
|
||||
|
|
@ -995,7 +1004,7 @@ test.concurrent(
|
|||
},
|
||||
);
|
||||
|
||||
test.concurrent('approve failed schema check with a comment', async () => {
|
||||
test.concurrent('approve failed schema check with a comment', async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, target } = await createProject(ProjectType.Single);
|
||||
|
|
@ -1106,7 +1115,7 @@ test.concurrent('approve failed schema check with a comment', async () => {
|
|||
|
||||
test.concurrent(
|
||||
'approving a schema check with contextId containing breaking changes allows the changes for subsequent checks with the same contextId',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, target } = await createProject(ProjectType.Single);
|
||||
|
|
@ -1256,7 +1265,7 @@ test.concurrent(
|
|||
|
||||
test.concurrent(
|
||||
'approving a schema check with contextId containing breaking changes does not allow the changes for subsequent checks with a different contextId',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, target } = await createProject(ProjectType.Single);
|
||||
|
|
@ -1399,7 +1408,7 @@ test.concurrent(
|
|||
|
||||
test.concurrent(
|
||||
'subsequent schema check with shared contextId that contains new breaking changes that have not been approved fails',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, target } = await createProject(ProjectType.Single);
|
||||
|
|
@ -1552,72 +1561,75 @@ test.concurrent(
|
|||
},
|
||||
);
|
||||
|
||||
test.concurrent('contextId that has more than 300 characters is not allowed', async () => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(ProjectType.Single);
|
||||
test.concurrent(
|
||||
'contextId that has more than 300 characters is not allowed',
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(ProjectType.Single);
|
||||
|
||||
// Create a token with write rights
|
||||
const writeToken = await createToken({
|
||||
targetScopes: [
|
||||
TargetAccessScope.Read,
|
||||
TargetAccessScope.RegistryRead,
|
||||
TargetAccessScope.RegistryWrite,
|
||||
TargetAccessScope.Settings,
|
||||
],
|
||||
});
|
||||
|
||||
// Publish schema with write rights
|
||||
const publishResult = await writeToken
|
||||
.publishSchema({
|
||||
sdl: /* GraphQL */ `
|
||||
type Query {
|
||||
ping: String
|
||||
pong: String
|
||||
}
|
||||
`,
|
||||
})
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
// Schema publish should be successful
|
||||
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
||||
|
||||
// Create a token with read rights
|
||||
const readToken = await createToken({
|
||||
targetScopes: [TargetAccessScope.RegistryRead],
|
||||
projectScopes: [],
|
||||
organizationScopes: [],
|
||||
});
|
||||
|
||||
const contextId = '';
|
||||
|
||||
// Check schema with read rights
|
||||
const checkResult = await readToken
|
||||
.checkSchema(
|
||||
/* GraphQL */ `
|
||||
type Query {
|
||||
ping: Float
|
||||
}
|
||||
`,
|
||||
undefined,
|
||||
undefined,
|
||||
contextId,
|
||||
)
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
expect(checkResult.schemaCheck).toMatchObject({
|
||||
__typename: 'SchemaCheckError',
|
||||
errors: {
|
||||
nodes: [
|
||||
{
|
||||
message: 'Context ID must be at least 1 character long.',
|
||||
},
|
||||
// Create a token with write rights
|
||||
const writeToken = await createToken({
|
||||
targetScopes: [
|
||||
TargetAccessScope.Read,
|
||||
TargetAccessScope.RegistryRead,
|
||||
TargetAccessScope.RegistryWrite,
|
||||
TargetAccessScope.Settings,
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('contextId that has fewer than 1 characters is not allowed', async () => {
|
||||
// Publish schema with write rights
|
||||
const publishResult = await writeToken
|
||||
.publishSchema({
|
||||
sdl: /* GraphQL */ `
|
||||
type Query {
|
||||
ping: String
|
||||
pong: String
|
||||
}
|
||||
`,
|
||||
})
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
// Schema publish should be successful
|
||||
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
||||
|
||||
// Create a token with read rights
|
||||
const readToken = await createToken({
|
||||
targetScopes: [TargetAccessScope.RegistryRead],
|
||||
projectScopes: [],
|
||||
organizationScopes: [],
|
||||
});
|
||||
|
||||
const contextId = '';
|
||||
|
||||
// Check schema with read rights
|
||||
const checkResult = await readToken
|
||||
.checkSchema(
|
||||
/* GraphQL */ `
|
||||
type Query {
|
||||
ping: Float
|
||||
}
|
||||
`,
|
||||
undefined,
|
||||
undefined,
|
||||
contextId,
|
||||
)
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
expect(checkResult.schemaCheck).toMatchObject({
|
||||
__typename: 'SchemaCheckError',
|
||||
errors: {
|
||||
nodes: [
|
||||
{
|
||||
message: 'Context ID must be at least 1 character long.',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.concurrent('contextId that has fewer than 1 characters is not allowed', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(ProjectType.Single);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ describe.each`
|
|||
${ProjectType.Stitching} | ${'legacy'}
|
||||
${ProjectType.Federation} | ${'legacy'}
|
||||
`('$projectType ($model)', ({ projectType, model }) => {
|
||||
test.concurrent('should insert lowercase service name to DB', async () => {
|
||||
test.concurrent('should insert lowercase service name to DB', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(projectType, {
|
||||
|
|
@ -141,7 +141,7 @@ describe.each`
|
|||
`('$projectType', ({ projectType }) => {
|
||||
test.concurrent(
|
||||
'should publish A, publish B, delete B, publish A and have A and B at the end',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(projectType);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { ProjectAccessScope, ProjectType, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { enableExternalSchemaComposition } from '../../../testkit/flow';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
import { generateUnique } from '../../../testkit/utils';
|
||||
import { generateUnique, getServiceHost } from '../../../testkit/utils';
|
||||
|
||||
// We do not resolve this to a host address, because we are calling this through a different flow:
|
||||
// GraphQL API -> Schema service -> Composition service
|
||||
const dockerAddress = `composition_federation_2:3069`;
|
||||
const dockerAddress = await getServiceHost('composition_federation_2', 3069, false);
|
||||
|
||||
test.concurrent('call an external service to compose and validate services', async () => {
|
||||
test.concurrent('call an external service to compose and validate services', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, setNativeFederation } = await createProject(ProjectType.Federation);
|
||||
|
|
|
|||
|
|
@ -393,7 +393,7 @@ const SchemaCheckQuery = graphql(/* GraphQL */ `
|
|||
|
||||
test.concurrent(
|
||||
'approve failed schema check that has breaking change in contract check -> updates the status to successful and attaches meta information to the breaking change',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { createProject, organization, setFeatureFlag } = await createOrg();
|
||||
const { createToken, project, target, setNativeFederation } = await createProject(
|
||||
|
|
@ -566,7 +566,7 @@ test.concurrent(
|
|||
|
||||
test.concurrent(
|
||||
'approving a schema check with contextId containing breaking changes allows the changes for subsequent checks with the same contextId',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { createProject, organization, setFeatureFlag } = await createOrg();
|
||||
const { createToken, project, target, setNativeFederation } = await createProject(
|
||||
|
|
@ -771,7 +771,7 @@ test.concurrent(
|
|||
|
||||
test.concurrent(
|
||||
'approving a schema check with contextId containing breaking changes does not allow the changes for subsequent checks with a different contextId',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { createProject, organization, setFeatureFlag } = await createOrg();
|
||||
const { createToken, project, target, setNativeFederation } = await createProject(
|
||||
|
|
@ -969,7 +969,7 @@ test.concurrent(
|
|||
|
||||
test.concurrent(
|
||||
'subsequent schema check with shared contextId that contains new breaking changes that have not been approved fails',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { createProject, organization, setFeatureFlag } = await createOrg();
|
||||
const { createToken, project, target, setNativeFederation } = await createProject(
|
||||
|
|
@ -1178,7 +1178,7 @@ test.concurrent(
|
|||
|
||||
test.concurrent(
|
||||
'schema check that has no composition errors in contract check -> can be approved',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { createProject, organization, setFeatureFlag } = await createOrg();
|
||||
const { createToken, project, target, setNativeFederation } = await createProject(
|
||||
|
|
@ -1296,7 +1296,7 @@ test.concurrent(
|
|||
|
||||
test.concurrent(
|
||||
'schema check that has composition errors in contract check -> can not be approved',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { createProject, organization, setFeatureFlag } = await createOrg();
|
||||
const { createToken, project, target, setNativeFederation } = await createProject(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { parse, print } from 'graphql';
|
|||
import { enableExternalSchemaComposition } from 'testkit/flow';
|
||||
import { ProjectAccessScope, ProjectType, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { initSeed } from 'testkit/seed';
|
||||
import { getServiceHost } from 'testkit/utils';
|
||||
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
||||
import { createStorage } from '@hive/storage';
|
||||
import { sortSDL } from '@theguild/federation-composition';
|
||||
|
|
@ -63,7 +64,7 @@ function normalizeSDL(sdl: string): string {
|
|||
|
||||
test.concurrent(
|
||||
'can delete a service and updates the CDN when the super schema is still composable',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken, target } = await createProject(ProjectType.Federation);
|
||||
|
|
@ -245,7 +246,7 @@ test.concurrent(
|
|||
|
||||
await enableExternalSchemaComposition(
|
||||
{
|
||||
endpoint: `http://${`composition_federation_2:3069`}/compose`,
|
||||
endpoint: `http://${await getServiceHost('composition_federation_2', 3069, false)}/compose`,
|
||||
// eslint-disable-next-line no-process-env
|
||||
secret: process.env.EXTERNAL_COMPOSITION_SECRET!,
|
||||
project: project.cleanId,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { ProjectAccessScope, ProjectType, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { history, serviceName, servicePort } from '../../../testkit/external-composition';
|
||||
import { history } from '../../../testkit/external-composition';
|
||||
import { enableExternalSchemaComposition } from '../../../testkit/flow';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
import { generateUnique } from '../../../testkit/utils';
|
||||
import { generateUnique, getServiceHost } from '../../../testkit/utils';
|
||||
|
||||
test.concurrent('call an external service to compose and validate services', async () => {
|
||||
test.concurrent('call an external service to compose and validate services', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, setNativeFederation } = await createProject(ProjectType.Federation);
|
||||
|
|
@ -43,7 +43,7 @@ test.concurrent('call an external service to compose and validate services', asy
|
|||
|
||||
// we use internal docker network to connect to the external composition service,
|
||||
// so we need to use the name and not resolved host
|
||||
const dockerAddress = `${serviceName}:${servicePort}`;
|
||||
const dockerAddress = await getServiceHost('external_composition', 3012, false);
|
||||
// enable external composition
|
||||
const externalCompositionResult = await enableExternalSchemaComposition(
|
||||
{
|
||||
|
|
@ -91,7 +91,7 @@ test.concurrent('call an external service to compose and validate services', asy
|
|||
|
||||
test.concurrent(
|
||||
'an expected error coming from the external composition service should be visible to the user',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, setNativeFederation } = await createProject(
|
||||
|
|
@ -131,7 +131,7 @@ test.concurrent(
|
|||
|
||||
// we use internal docker network to connect to the external composition service,
|
||||
// so we need to use the name and not resolved host
|
||||
const dockerAddress = `${serviceName}:${servicePort}`;
|
||||
const dockerAddress = await getServiceHost('external_composition', 3012, false);
|
||||
// enable external composition
|
||||
const externalCompositionResult = await enableExternalSchemaComposition(
|
||||
{
|
||||
|
|
@ -191,7 +191,7 @@ test.concurrent(
|
|||
|
||||
test.concurrent(
|
||||
'a network error coming from the external composition service should be visible to the user',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, setNativeFederation } = await createProject(
|
||||
|
|
@ -231,7 +231,7 @@ test.concurrent(
|
|||
|
||||
// we use internal docker network to connect to the external composition service,
|
||||
// so we need to use the name and not resolved host
|
||||
const dockerAddress = `${serviceName}:${servicePort}`;
|
||||
const dockerAddress = await getServiceHost('external_composition', 3012, false);
|
||||
// enable external composition
|
||||
const externalCompositionResult = await enableExternalSchemaComposition(
|
||||
{
|
||||
|
|
@ -326,7 +326,7 @@ test.concurrent('a timeout error should be visible to the user', async ({ expect
|
|||
|
||||
// we use internal docker network to connect to the external composition service,
|
||||
// so we need to use the name and not resolved host
|
||||
const dockerAddress = `${serviceName}:${servicePort}`;
|
||||
const dockerAddress = await getServiceHost('external_composition', 3012, false);
|
||||
// enable external composition
|
||||
const externalCompositionResult = await enableExternalSchemaComposition(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { graphql } from 'testkit/gql';
|
|||
/* eslint-disable no-process-env */
|
||||
import { ProjectAccessScope, ProjectType, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { execute } from 'testkit/graphql';
|
||||
import { getServiceHost } from 'testkit/utils';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { createStorage } from '@hive/storage';
|
||||
import {
|
||||
|
|
@ -13,31 +14,34 @@ import {
|
|||
} from '../../../testkit/flow';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
|
||||
test.concurrent('cannot publish a schema without target:registry:write access', async () => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(ProjectType.Federation);
|
||||
const readToken = await createToken({
|
||||
targetScopes: [TargetAccessScope.RegistryRead],
|
||||
projectScopes: [],
|
||||
organizationScopes: [],
|
||||
});
|
||||
test.concurrent(
|
||||
'cannot publish a schema without target:registry:write access',
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(ProjectType.Federation);
|
||||
const readToken = await createToken({
|
||||
targetScopes: [TargetAccessScope.RegistryRead],
|
||||
projectScopes: [],
|
||||
organizationScopes: [],
|
||||
});
|
||||
|
||||
const resultErrors = await readToken
|
||||
.publishSchema({
|
||||
sdl: /* GraphQL */ `
|
||||
type Query {
|
||||
ping: String
|
||||
}
|
||||
`,
|
||||
})
|
||||
.then(r => r.expectGraphQLErrors());
|
||||
const resultErrors = await readToken
|
||||
.publishSchema({
|
||||
sdl: /* GraphQL */ `
|
||||
type Query {
|
||||
ping: String
|
||||
}
|
||||
`,
|
||||
})
|
||||
.then(r => r.expectGraphQLErrors());
|
||||
|
||||
expect(resultErrors).toHaveLength(1);
|
||||
expect(resultErrors[0].message).toMatch('target:registry:write');
|
||||
});
|
||||
expect(resultErrors).toHaveLength(1);
|
||||
expect(resultErrors[0].message).toMatch('target:registry:write');
|
||||
},
|
||||
);
|
||||
|
||||
test.concurrent('can publish a schema with target:registry:write access', async () => {
|
||||
test.concurrent('can publish a schema with target:registry:write access', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(ProjectType.Single);
|
||||
|
|
@ -76,63 +80,66 @@ test.concurrent('can publish a schema with target:registry:write access', async
|
|||
expect(versionsResult).toHaveLength(2);
|
||||
});
|
||||
|
||||
test.concurrent('base schema should not affect the output schema persisted in db', async () => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(ProjectType.Single);
|
||||
const readWriteToken = await createToken({
|
||||
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
|
||||
projectScopes: [],
|
||||
organizationScopes: [],
|
||||
});
|
||||
test.concurrent(
|
||||
'base schema should not affect the output schema persisted in db',
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(ProjectType.Single);
|
||||
const readWriteToken = await createToken({
|
||||
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
|
||||
projectScopes: [],
|
||||
organizationScopes: [],
|
||||
});
|
||||
|
||||
// Publish schema with write rights
|
||||
const publishResult = await readWriteToken
|
||||
.publishSchema({
|
||||
commit: '1',
|
||||
sdl: `type Query { ping: String }`,
|
||||
})
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
// Publish schema with write rights
|
||||
const publishResult = await readWriteToken
|
||||
.publishSchema({
|
||||
commit: '1',
|
||||
sdl: `type Query { ping: String }`,
|
||||
})
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
// Schema publish should be successful
|
||||
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
||||
// Schema publish should be successful
|
||||
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
||||
|
||||
await readWriteToken.updateBaseSchema(`
|
||||
await readWriteToken.updateBaseSchema(`
|
||||
directive @auth on OBJECT | FIELD_DEFINITION
|
||||
`);
|
||||
|
||||
const extendedPublishResult = await readWriteToken
|
||||
.publishSchema({
|
||||
commit: '2',
|
||||
sdl: `type Query { ping: String @auth pong: String }`,
|
||||
})
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
expect(extendedPublishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
||||
const extendedPublishResult = await readWriteToken
|
||||
.publishSchema({
|
||||
commit: '2',
|
||||
sdl: `type Query { ping: String @auth pong: String }`,
|
||||
})
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
expect(extendedPublishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
||||
|
||||
const versionsResult = await readWriteToken.fetchVersions(5);
|
||||
expect(versionsResult).toHaveLength(2);
|
||||
const versionsResult = await readWriteToken.fetchVersions(5);
|
||||
expect(versionsResult).toHaveLength(2);
|
||||
|
||||
const latestResult = await readWriteToken.latestSchema();
|
||||
expect(latestResult.latestVersion?.schemas.total).toBe(1);
|
||||
const latestResult = await readWriteToken.latestSchema();
|
||||
expect(latestResult.latestVersion?.schemas.total).toBe(1);
|
||||
|
||||
const firstNode = latestResult.latestVersion?.schemas.nodes[0];
|
||||
const firstNode = latestResult.latestVersion?.schemas.nodes[0];
|
||||
|
||||
expect(firstNode).toEqual(
|
||||
expect.objectContaining({
|
||||
commit: '2',
|
||||
source: expect.stringContaining('type Query { ping: String @auth pong: String }'),
|
||||
}),
|
||||
);
|
||||
expect(firstNode).not.toEqual(
|
||||
expect.objectContaining({
|
||||
source: expect.stringContaining('directive'),
|
||||
}),
|
||||
);
|
||||
expect(firstNode).toEqual(
|
||||
expect.objectContaining({
|
||||
commit: '2',
|
||||
source: expect.stringContaining('type Query { ping: String @auth pong: String }'),
|
||||
}),
|
||||
);
|
||||
expect(firstNode).not.toEqual(
|
||||
expect.objectContaining({
|
||||
source: expect.stringContaining('directive'),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(latestResult.latestVersion?.baseSchema).toMatch(
|
||||
'directive @auth on OBJECT | FIELD_DEFINITION',
|
||||
);
|
||||
});
|
||||
expect(latestResult.latestVersion?.baseSchema).toMatch(
|
||||
'directive @auth on OBJECT | FIELD_DEFINITION',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test.concurrent.each(['legacy', 'modern'])(
|
||||
'directives should not be removed (federation %s)',
|
||||
|
|
@ -266,7 +273,7 @@ test.concurrent.each(['legacy', 'modern'])(
|
|||
},
|
||||
);
|
||||
|
||||
test.concurrent('share publication of schema using redis', async () => {
|
||||
test.concurrent('share publication of schema using redis', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(ProjectType.Federation);
|
||||
|
|
@ -318,7 +325,7 @@ test.concurrent('share publication of schema using redis', async () => {
|
|||
await expect(readWriteToken.fetchVersions(3)).resolves.toHaveLength(2);
|
||||
});
|
||||
|
||||
test.concurrent('CDN data can not be fetched with an invalid access token', async () => {
|
||||
test.concurrent('CDN data can not be fetched with an invalid access token', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(ProjectType.Single);
|
||||
|
|
@ -351,7 +358,7 @@ test.concurrent('CDN data can not be fetched with an invalid access token', asyn
|
|||
expect(res.status).toEqual(403);
|
||||
});
|
||||
|
||||
test.concurrent('CDN data can be fetched with an valid access token', async () => {
|
||||
test.concurrent('CDN data can be fetched with an valid access token', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(ProjectType.Single);
|
||||
|
|
@ -386,7 +393,7 @@ test.concurrent('CDN data can be fetched with an valid access token', async () =
|
|||
expect(cdnResult.status).toEqual(200);
|
||||
});
|
||||
|
||||
test.concurrent('cannot do API request with invalid access token', async () => {
|
||||
test.concurrent('cannot do API request with invalid access token', async ({ expect }) => {
|
||||
const errors = await publishSchema(
|
||||
{
|
||||
commit: '1',
|
||||
|
|
@ -407,7 +414,7 @@ test.concurrent('cannot do API request with invalid access token', async () => {
|
|||
|
||||
test.concurrent(
|
||||
'should publish only one schema if multiple same publishes are started in parallel',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(ProjectType.Single);
|
||||
|
|
@ -457,43 +464,46 @@ describe.each`
|
|||
};
|
||||
const serviceUrl = projectType === ProjectType.Single ? {} : { url: 'http://localhost:4000' };
|
||||
|
||||
test.concurrent('linkToWebsite should be available when publishing initial schema', async () => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { project, target, createToken } = await createProject(projectType, {
|
||||
useLegacyRegistryModels: model === 'legacy',
|
||||
});
|
||||
const readWriteToken = await createToken({
|
||||
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
|
||||
projectScopes: [],
|
||||
organizationScopes: [],
|
||||
});
|
||||
test.concurrent(
|
||||
'linkToWebsite should be available when publishing initial schema',
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { project, target, createToken } = await createProject(projectType, {
|
||||
useLegacyRegistryModels: model === 'legacy',
|
||||
});
|
||||
const readWriteToken = await createToken({
|
||||
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
|
||||
projectScopes: [],
|
||||
organizationScopes: [],
|
||||
});
|
||||
|
||||
const result = await readWriteToken
|
||||
.publishSchema({
|
||||
author: 'Kamil',
|
||||
commit: 'abc123',
|
||||
sdl: `type Query { ping: String }`,
|
||||
...serviceName,
|
||||
...serviceUrl,
|
||||
})
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
const result = await readWriteToken
|
||||
.publishSchema({
|
||||
author: 'Kamil',
|
||||
commit: 'abc123',
|
||||
sdl: `type Query { ping: String }`,
|
||||
...serviceName,
|
||||
...serviceUrl,
|
||||
})
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
expect(result.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
||||
expect(result.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
||||
|
||||
const linkToWebsite =
|
||||
result.schemaPublish.__typename === 'SchemaPublishSuccess'
|
||||
? result.schemaPublish.linkToWebsite
|
||||
: null;
|
||||
const linkToWebsite =
|
||||
result.schemaPublish.__typename === 'SchemaPublishSuccess'
|
||||
? result.schemaPublish.linkToWebsite
|
||||
: null;
|
||||
|
||||
expect(linkToWebsite).toEqual(
|
||||
`${process.env.HIVE_APP_BASE_URL}/${organization.cleanId}/${project.cleanId}/${target.cleanId}`,
|
||||
);
|
||||
});
|
||||
expect(linkToWebsite).toEqual(
|
||||
`${process.env.HIVE_APP_BASE_URL}/${organization.cleanId}/${project.cleanId}/${target.cleanId}`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test.concurrent(
|
||||
'linkToWebsite should be available when publishing non-initial schema',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, target } = await createProject(projectType, {
|
||||
|
|
@ -635,7 +645,7 @@ describe('schema publishing changes are persisted', () => {
|
|||
/** Only provide if you want to test a service url change */
|
||||
serviceUrlAfter?: string;
|
||||
}) {
|
||||
test.concurrent(`[Schema change] ${args.name}`, async () => {
|
||||
test.concurrent(`[Schema change] ${args.name}`, async ({ expect }) => {
|
||||
const serviceName = {
|
||||
service: 'test',
|
||||
};
|
||||
|
|
@ -2819,7 +2829,7 @@ test('Target.schemaVersion: result is read from the database', async () => {
|
|||
|
||||
test('Composition Error (Federation 2) can be served from the database', async () => {
|
||||
const storage = await createStorage(connectionString(), 1);
|
||||
const dockerAddress = `composition_federation_2:3069`;
|
||||
const serviceAddress = await getServiceHost('composition_federation_2', 3069, false);
|
||||
|
||||
try {
|
||||
const initialSchema = /* GraphQL */ `
|
||||
|
|
@ -2880,7 +2890,7 @@ test('Composition Error (Federation 2) can be served from the database', async (
|
|||
|
||||
await enableExternalSchemaComposition(
|
||||
{
|
||||
endpoint: `http://${dockerAddress}/compose`,
|
||||
endpoint: `http://${serviceAddress}/compose`,
|
||||
// eslint-disable-next-line no-process-env
|
||||
secret: process.env.EXTERNAL_COMPOSITION_SECRET!,
|
||||
project: project.cleanId,
|
||||
|
|
@ -2949,7 +2959,7 @@ test('Composition Error (Federation 2) can be served from the database', async (
|
|||
|
||||
test('Composition Network Failure (Federation 2)', async () => {
|
||||
const storage = await createStorage(connectionString(), 1);
|
||||
const dockerAddress = `composition_federation_2:3069`;
|
||||
const serviceAddress = await getServiceHost('composition_federation_2', 3069, false);
|
||||
|
||||
try {
|
||||
const initialSchema = /* GraphQL */ `
|
||||
|
|
@ -3009,7 +3019,7 @@ test('Composition Network Failure (Federation 2)', async () => {
|
|||
|
||||
await enableExternalSchemaComposition(
|
||||
{
|
||||
endpoint: `http://${dockerAddress}/compose`,
|
||||
endpoint: `http://${serviceAddress}/compose`,
|
||||
// eslint-disable-next-line no-process-env
|
||||
secret: process.env.EXTERNAL_COMPOSITION_SECRET!,
|
||||
project: project.cleanId,
|
||||
|
|
@ -3050,7 +3060,7 @@ test('Composition Network Failure (Federation 2)', async () => {
|
|||
|
||||
await enableExternalSchemaComposition(
|
||||
{
|
||||
endpoint: `http://${dockerAddress}/no_compose`,
|
||||
endpoint: `http://${serviceAddress}/no_compose`,
|
||||
secret: process.env.EXTERNAL_COMPOSITION_SECRET!,
|
||||
project: project.cleanId,
|
||||
organization: organization.cleanId,
|
||||
|
|
@ -3574,7 +3584,7 @@ test.concurrent(
|
|||
|
||||
test.concurrent(
|
||||
'publishing Federation schema results in tags stored on the schema version',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, setFeatureFlag } = await createOrg();
|
||||
const { createToken, setNativeFederation } = await createProject(ProjectType.Federation);
|
||||
|
|
@ -3609,7 +3619,7 @@ test.concurrent(
|
|||
},
|
||||
);
|
||||
|
||||
test.concurrent('CDN services are published in alphanumeric order', async () => {
|
||||
test.concurrent('CDN services are published in alphanumeric order', async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(ProjectType.Stitching);
|
||||
|
|
@ -3670,7 +3680,7 @@ test.concurrent('CDN services are published in alphanumeric order', async () =>
|
|||
|
||||
test.concurrent(
|
||||
'Composite schema project publish without service name results in error',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(ProjectType.Federation);
|
||||
|
|
@ -3701,7 +3711,7 @@ test.concurrent(
|
|||
|
||||
test.concurrent(
|
||||
'Composite schema project publish without service url results in error',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(ProjectType.Federation);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { ProjectType } from 'testkit/gql/graphql';
|
|||
import { renameTarget } from '../../../testkit/flow';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
|
||||
test.concurrent('renaming a target should result changing its cleanId', async () => {
|
||||
test.concurrent('renaming a target should result changing its cleanId', async ({ expect }) => {
|
||||
const { createOrg, ownerToken } = await initSeed().createOwner();
|
||||
const { organization, createProject } = await createOrg();
|
||||
const { project, target } = await createProject(ProjectType.Single);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { initSeed } from '../../../testkit/seed';
|
|||
|
||||
test.concurrent(
|
||||
'setting no scopes equals to readonly for organization, project, target',
|
||||
async () => {
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject } = await createOrg();
|
||||
const { createToken } = await createProject(ProjectType.Single);
|
||||
|
|
@ -43,22 +43,25 @@ test.concurrent(
|
|||
},
|
||||
);
|
||||
|
||||
test.concurrent('cannot set a scope on a token if user has no access to that scope', async () => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, inviteAndJoinMember } = await createOrg();
|
||||
const { createToken, target } = await createProject(ProjectType.Single);
|
||||
const { memberToken } = await inviteAndJoinMember();
|
||||
test.concurrent(
|
||||
'cannot set a scope on a token if user has no access to that scope',
|
||||
async ({ expect }) => {
|
||||
const { createOrg } = await initSeed().createOwner();
|
||||
const { createProject, inviteAndJoinMember } = await createOrg();
|
||||
const { createToken, target } = await createProject(ProjectType.Single);
|
||||
const { memberToken } = await inviteAndJoinMember();
|
||||
|
||||
// member should not have access to target:registry:write (as it's only a Viewer)
|
||||
const tokenResult = createToken({
|
||||
targetScopes: [TargetAccessScope.RegistryWrite],
|
||||
projectScopes: [],
|
||||
organizationScopes: [],
|
||||
target,
|
||||
actorToken: memberToken,
|
||||
});
|
||||
// member should not have access to target:registry:write (as it's only a Viewer)
|
||||
const tokenResult = createToken({
|
||||
targetScopes: [TargetAccessScope.RegistryWrite],
|
||||
projectScopes: [],
|
||||
organizationScopes: [],
|
||||
target,
|
||||
actorToken: memberToken,
|
||||
});
|
||||
|
||||
await expect(tokenResult).rejects.toThrowError(
|
||||
'No access (reason: "Missing target:tokens:write permission")',
|
||||
);
|
||||
});
|
||||
await expect(tokenResult).rejects.toThrowError(
|
||||
'No access (reason: "Missing target:tokens:write permission")',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,5 @@
|
|||
import { ProjectType, RegistryModel } from 'testkit/gql/graphql';
|
||||
import { normalizeCliOutput } from '../../../scripts/serializers/cli-output';
|
||||
import { createCLI, schemaPublish } from '../../testkit/cli';
|
||||
import { prepareProject } from '../../testkit/registry-models';
|
||||
import { initSeed } from '../../testkit/seed';
|
||||
|
|
@ -267,48 +268,47 @@ describe('publish', () => {
|
|||
serviceUrl: 'http://products:3000/graphql',
|
||||
};
|
||||
|
||||
await expect(
|
||||
publish({
|
||||
sdl: /* GraphQL */ `
|
||||
type Query {
|
||||
topProduct: Product
|
||||
}
|
||||
const out = publish({
|
||||
sdl: /* GraphQL */ `
|
||||
type Query {
|
||||
topProduct: Product
|
||||
}
|
||||
|
||||
type Product {
|
||||
id: ID!
|
||||
name: String!
|
||||
}
|
||||
`,
|
||||
...service,
|
||||
expect: 'latest-composable',
|
||||
}),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
type Product {
|
||||
id: ID!
|
||||
name: String!
|
||||
}
|
||||
`,
|
||||
...service,
|
||||
expect: 'latest-composable',
|
||||
}).then(normalizeCliOutput);
|
||||
|
||||
await expect(out).resolves.toMatchInlineSnapshot(`
|
||||
v Published initial schema.
|
||||
i Available at http://localhost:8080/$organization/$project/$target
|
||||
i Available at $appUrl/$organization/$project/$target
|
||||
`);
|
||||
|
||||
await expect(
|
||||
publish({
|
||||
sdl: /* GraphQL */ `
|
||||
type Query {
|
||||
topProduct: Product
|
||||
}
|
||||
const out2 = publish({
|
||||
sdl: /* GraphQL */ `
|
||||
type Query {
|
||||
topProduct: Product
|
||||
}
|
||||
|
||||
type Product {
|
||||
id: ID!
|
||||
name: String!
|
||||
price: Int!
|
||||
}
|
||||
`,
|
||||
...service,
|
||||
expect: 'latest-composable',
|
||||
}),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
type Product {
|
||||
id: ID!
|
||||
name: String!
|
||||
price: Int!
|
||||
}
|
||||
`,
|
||||
...service,
|
||||
expect: 'latest-composable',
|
||||
}).then(normalizeCliOutput);
|
||||
await expect(out2).resolves.toMatchInlineSnapshot(`
|
||||
i Detected 1 change
|
||||
Safe changes:
|
||||
- Field price was added to object type Product
|
||||
v Schema published
|
||||
i Available at http://localhost:8080/$organization/$project/$target/history/$version
|
||||
i Available at $appUrl/$organization/$project/$target/history/$version
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -301,9 +301,7 @@ describe('publish', () => {
|
|||
|
||||
expect(output).toEqual(expect.stringContaining(`v Published initial schema.`));
|
||||
expect(output).toEqual(
|
||||
expect.stringContaining(
|
||||
`i Available at http://localhost:8080/$organization/$project/$target`,
|
||||
),
|
||||
expect.stringContaining(`i Available at $appUrl/$organization/$project/$target`),
|
||||
);
|
||||
|
||||
output = normalizeCliOutput(
|
||||
|
|
@ -327,7 +325,7 @@ describe('publish', () => {
|
|||
expect(output).toEqual(expect.stringContaining(`v Schema published`));
|
||||
expect(output).toEqual(
|
||||
expect.stringContaining(
|
||||
`i Available at http://localhost:8080/$organization/$project/$target/history/$version`,
|
||||
`i Available at $appUrl/$organization/$project/$target/history/$version`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -307,9 +307,7 @@ describe('publish', () => {
|
|||
|
||||
expect(output).toEqual(expect.stringContaining(`v Published initial schema.`));
|
||||
expect(output).toEqual(
|
||||
expect.stringContaining(
|
||||
`i Available at http://localhost:8080/$organization/$project/$target`,
|
||||
),
|
||||
expect.stringContaining(`i Available at $appUrl/$organization/$project/$target`),
|
||||
);
|
||||
|
||||
output = normalizeCliOutput(
|
||||
|
|
@ -333,7 +331,7 @@ describe('publish', () => {
|
|||
expect(output).toEqual(expect.stringContaining(`v Schema published`));
|
||||
expect(output).toEqual(
|
||||
expect.stringContaining(
|
||||
`i Available at http://localhost:8080/$organization/$project/$target/history/$version`,
|
||||
`i Available at $appUrl/$organization/$project/$target/history/$version`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ describe('publish', () => {
|
|||
}),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
v Published initial schema.
|
||||
i Available at http://localhost:8080/$organization/$project/$target
|
||||
i Available at $appUrl/$organization/$project/$target
|
||||
`);
|
||||
|
||||
await expect(
|
||||
|
|
@ -172,7 +172,7 @@ describe('publish', () => {
|
|||
Safe changes:
|
||||
- Field price was added to object type Product
|
||||
v Schema published
|
||||
i Available at http://localhost:8080/$organization/$project/$target/history/$version
|
||||
i Available at $appUrl/$organization/$project/$target/history/$version
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -143,9 +143,7 @@ describe('publish', () => {
|
|||
|
||||
expect(output).toEqual(expect.stringContaining(`v Published initial schema.`));
|
||||
expect(output).toEqual(
|
||||
expect.stringContaining(
|
||||
`i Available at http://localhost:8080/$organization/$project/$target`,
|
||||
),
|
||||
expect.stringContaining(`i Available at $appUrl/$organization/$project/$target`),
|
||||
);
|
||||
|
||||
output = normalizeCliOutput(
|
||||
|
|
@ -168,7 +166,7 @@ describe('publish', () => {
|
|||
expect(output).toEqual(expect.stringContaining(`v Schema published`));
|
||||
expect(output).toEqual(
|
||||
expect.stringContaining(
|
||||
`i Available at http://localhost:8080/$organization/$project/$target/history/$version`,
|
||||
`i Available at $appUrl/$organization/$project/$target/history/$version`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ describe('publish', () => {
|
|||
}),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
v Published initial schema.
|
||||
i Available at http://localhost:8080/$organization/$project/$target
|
||||
i Available at $appUrl/$organization/$project/$target
|
||||
`);
|
||||
|
||||
await expect(
|
||||
|
|
@ -277,7 +277,7 @@ describe('publish', () => {
|
|||
Safe changes:
|
||||
- Field price was added to object type Product
|
||||
v Schema published
|
||||
i Available at http://localhost:8080/$organization/$project/$target/history/$version
|
||||
i Available at $appUrl/$organization/$project/$target/history/$version
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -268,9 +268,7 @@ describe('publish', () => {
|
|||
|
||||
expect(output).toEqual(expect.stringContaining('v Published initial schema.'));
|
||||
expect(output).toEqual(
|
||||
expect.stringContaining(
|
||||
'i Available at http://localhost:8080/$organization/$project/$target',
|
||||
),
|
||||
expect.stringContaining('i Available at $appUrl/$organization/$project/$target'),
|
||||
);
|
||||
|
||||
output = normalizeCliOutput(
|
||||
|
|
@ -294,7 +292,7 @@ describe('publish', () => {
|
|||
expect(output).toEqual(expect.stringContaining(`v Schema published`));
|
||||
expect(output).toEqual(
|
||||
expect.stringContaining(
|
||||
`i Available at http://localhost:8080/$organization/$project/$target/history/$version`,
|
||||
`i Available at $appUrl/$organization/$project/$target/history/$version`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import { getServiceHost } from 'testkit/utils';
|
||||
import type { SchemaBuilderApi } from '@hive/schema';
|
||||
import { createTRPCProxyClient, httpLink } from '@trpc/client';
|
||||
|
||||
const host = process.env['SCHEMA_SERVICE_HOST_OVERRIDE'] || 'http://localhost:3002';
|
||||
const host =
|
||||
process.env['SCHEMA_SERVICE_HOST_OVERRIDE'] ||
|
||||
(await getServiceHost('schema', 3002).then(r => `http://${r}`));
|
||||
|
||||
const client = createTRPCProxyClient<SchemaBuilderApi>({
|
||||
links: [
|
||||
|
|
|
|||
|
|
@ -12,5 +12,5 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"include": ["./testkit", "./tests", "./expect.ts"]
|
||||
"include": ["./testkit", "./tests", "./expect.ts", "local-dev.ts"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
const setupFiles = ['../scripts/serializer.ts', './expect.ts'];
|
||||
|
||||
if (!process.env.RUN_AGAINST_LOCAL_SERVICES) {
|
||||
setupFiles.unshift('dotenv/config');
|
||||
} else {
|
||||
setupFiles.unshift('./local-dev.ts');
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
|
|
@ -11,7 +19,7 @@ export default defineConfig({
|
|||
import.meta.url,
|
||||
).pathname,
|
||||
},
|
||||
setupFiles: ['dotenv/config', '../scripts/serializer.ts', './expect.ts'],
|
||||
setupFiles,
|
||||
testTimeout: 90_000,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
"build:services": "pnpm prebuild && pnpm turbo build --filter=./packages/services/**/* --filter=./packages/migrations --color",
|
||||
"build:web": "pnpm prebuild && pnpm turbo build --filter=./packages/web/* --color",
|
||||
"cargo:fix": "bash ./scripts/fix-symbolic-link.sh",
|
||||
"dev:integration": "cd integration-tests && pnpm dev:integration",
|
||||
"docker:build": "docker buildx bake -f docker/docker.hcl --load build",
|
||||
"docker:override-up": "docker compose -f ./docker/docker-compose.override.yml up -d --remove-orphans",
|
||||
"env:sync": "tsx scripts/sync-env-files.ts",
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export const version = '0.33.3';
|
||||
export const version = '0.33.4';
|
||||
|
|
|
|||
|
|
@ -71,7 +71,8 @@
|
|||
"graphql": "16.9.0",
|
||||
"oclif": "4.13.6",
|
||||
"rimraf": "4.4.1",
|
||||
"tsx": "4.16.2"
|
||||
"tsx": "4.16.2",
|
||||
"typescript": "5.5.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npmjs.org",
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export const version = '0.4.0';
|
||||
export const version = '0.5.0';
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export const version = '0.33.2';
|
||||
export const version = '0.33.3';
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@
|
|||
"typings": "dist/typings/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "bob build && node build-example.mjs",
|
||||
"check:build": "bob check"
|
||||
"check:build": "bob check",
|
||||
"example": "pnpm build && PORT=3012 SECRET=secretsecret node dist/example.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export const version = '0.33.2';
|
||||
export const version = '0.33.3';
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
SECRET=secretsecret
|
||||
|
|
@ -5,13 +5,14 @@
|
|||
"private": true,
|
||||
"scripts": {
|
||||
"build": "tsx ../../../../scripts/runify.ts",
|
||||
"dev": "tsup-node --config ../../../configs/tsup/dev.config.node.ts src/index.ts"
|
||||
"dev": "tsup-node --config ../../../configs/tsup/dev.config.node.ts src/dev.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apollo/composition": "2.8.3",
|
||||
"@apollo/federation-internals": "2.8.3",
|
||||
"@graphql-hive/external-composition": "workspace:*",
|
||||
"@whatwg-node/server": "0.9.36",
|
||||
"dotenv": "16.4.5",
|
||||
"graphql": "16.9.0",
|
||||
"lru-cache": "^7.17.0",
|
||||
"zod": "3.23.8"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
import { config } from 'dotenv';
|
||||
|
||||
config({
|
||||
debug: true,
|
||||
encoding: 'utf8',
|
||||
});
|
||||
|
||||
await import('./index');
|
||||
|
|
@ -7,4 +7,5 @@ POSTGRES_DB=registry
|
|||
USAGE_ESTIMATOR_ENDPOINT=http://localhost:4011
|
||||
EMAILS_ENDPOINT=http://localhost:6260
|
||||
WEB_APP_URL=http://localhost:3000
|
||||
OPENTELEMETRY_COLLECTOR_ENDPOINT="<sync>"
|
||||
OPENTELEMETRY_COLLECTOR_ENDPOINT="<sync>"
|
||||
LIMIT_CACHE_UPDATE_INTERVAL_MS=2000
|
||||
|
|
@ -362,11 +362,14 @@ async function callExternalService(
|
|||
}
|
||||
|
||||
logger.info(
|
||||
'Network error so return failure (status=%s, message=%s)',
|
||||
'Network error so return failure (url=%s, status=%s, message=%s)',
|
||||
input.url,
|
||||
error.response.statusCode,
|
||||
error.message,
|
||||
);
|
||||
|
||||
logger.error(error);
|
||||
|
||||
span.setAttribute('error.message', error.message || '');
|
||||
span.setAttribute('error.type', error.name);
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,10 @@ CDN_CF_AUTH_TOKEN=85e20c26c03759603c0f45884824a1c3
|
|||
CDN_CF_NAMESPACE_ID=33b1e3bbb4a4707d05ea0307cbb55c79
|
||||
CDN_BASE_URL="http://localhost:4010"
|
||||
|
||||
# Dev/Local CDN Configuration
|
||||
CDN_API=1
|
||||
CDN_API_BASE_URL=http://localhost:3001
|
||||
|
||||
# S3 Artifacts Configuration
|
||||
S3_ENDPOINT=http://localhost:9000
|
||||
S3_ACCESS_KEY_ID="minioadmin"
|
||||
|
|
@ -55,7 +59,7 @@ SUPERTOKENS_CONNECTION_URI=http://localhost:3567
|
|||
SUPERTOKENS_API_KEY=bubatzbieber6942096420
|
||||
|
||||
# Organization level Open ID Connect Authentication
|
||||
AUTH_ORGANIZATION_OIDC=0
|
||||
AUTH_ORGANIZATION_OIDC=1
|
||||
|
||||
## Enable GitHub login
|
||||
AUTH_GITHUB="<sync>"
|
||||
|
|
@ -77,4 +81,6 @@ AUTH_OKTA_ENDPOINT="<sync>"
|
|||
AUTH_OKTA_CLIENT_ID="<sync>"
|
||||
AUTH_OKTA_CLIENT_SECRET="<sync>"
|
||||
|
||||
OPENTELEMETRY_COLLECTOR_ENDPOINT="<sync>"
|
||||
OPENTELEMETRY_COLLECTOR_ENDPOINT="<sync>"
|
||||
|
||||
HIVE_ENCRYPTION_SECRET=wowverysecuremuchsecret
|
||||
|
|
@ -8,6 +8,6 @@ CLICKHOUSE_HOST="localhost"
|
|||
CLICKHOUSE_PORT="8123"
|
||||
CLICKHOUSE_USERNAME="test"
|
||||
CLICKHOUSE_PASSWORD="test"
|
||||
CLICKHOUSE_ASYNC_INSERT_BUSY_TIMEOUT_MS="30000"
|
||||
CLICKHOUSE_ASYNC_INSERT_MAX_DATA_SIZE="200000000"
|
||||
CLICKHOUSE_ASYNC_INSERT_BUSY_TIMEOUT_MS="500"
|
||||
CLICKHOUSE_ASYNC_INSERT_MAX_DATA_SIZE="1000"
|
||||
PORT=4002
|
||||
132
pnpm-lock.yaml
132
pnpm-lock.yaml
|
|
@ -446,6 +446,9 @@ importers:
|
|||
tsx:
|
||||
specifier: 4.16.2
|
||||
version: 4.16.2
|
||||
typescript:
|
||||
specifier: 5.5.3
|
||||
version: 5.5.3
|
||||
|
||||
packages/libraries/core:
|
||||
dependencies:
|
||||
|
|
@ -928,6 +931,9 @@ importers:
|
|||
'@whatwg-node/server':
|
||||
specifier: 0.9.36
|
||||
version: 0.9.36
|
||||
dotenv:
|
||||
specifier: 16.4.5
|
||||
version: 16.4.5
|
||||
graphql:
|
||||
specifier: 16.9.0
|
||||
version: 16.9.0
|
||||
|
|
@ -3311,22 +3317,10 @@ packages:
|
|||
'@babel/regjsgen@0.8.0':
|
||||
resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==}
|
||||
|
||||
'@babel/runtime@7.20.7':
|
||||
resolution: {integrity: sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/runtime@7.23.1':
|
||||
resolution: {integrity: sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/runtime@7.24.0':
|
||||
resolution: {integrity: sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/runtime@7.24.4':
|
||||
resolution: {integrity: sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/runtime@7.24.7':
|
||||
resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
|
@ -9809,6 +9803,9 @@ packages:
|
|||
domutils@3.0.1:
|
||||
resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==}
|
||||
|
||||
domutils@3.1.0:
|
||||
resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==}
|
||||
|
||||
dot-case@3.0.4:
|
||||
resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
|
||||
|
||||
|
|
@ -9904,6 +9901,10 @@ packages:
|
|||
resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
entities@4.5.0:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
env-ci@7.3.0:
|
||||
resolution: {integrity: sha512-L8vK54CSjKB4pwlwx0YaqeBdUSGufaLHl/pEgD+EqnMrYCVUA8HzMjURALSyvOlC57e953yN7KyXS63qDoc3Rg==}
|
||||
engines: {node: '>=12.20'}
|
||||
|
|
@ -11953,8 +11954,8 @@ packages:
|
|||
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
|
||||
engines: {node: '>=4.0'}
|
||||
|
||||
juice@9.0.0:
|
||||
resolution: {integrity: sha512-s/IwgQ4caZq3bSnQZlKfdGUqJWy9WzTzB12WSPko9G8uK74H8BJEQvX7GLmFAQ6SLFgAppqC/TUYepKZZaV+JA==}
|
||||
juice@9.1.0:
|
||||
resolution: {integrity: sha512-odblShmPrUoHUwRuC8EmLji5bPP2MLO1GL+gt4XU3tT2ECmbSrrMjtMQaqg3wgMFP2zvUzdPZGfxc5Trk3Z+fQ==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
hasBin: true
|
||||
|
||||
|
|
@ -14543,9 +14544,6 @@ packages:
|
|||
regenerate@1.4.2:
|
||||
resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==}
|
||||
|
||||
regenerator-runtime@0.13.11:
|
||||
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
|
||||
|
||||
regenerator-runtime@0.14.1:
|
||||
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
|
||||
|
||||
|
|
@ -17056,28 +17054,28 @@ snapshots:
|
|||
'@aws-sdk/util-endpoints': 3.587.0
|
||||
'@aws-sdk/util-user-agent-browser': 3.577.0
|
||||
'@aws-sdk/util-user-agent-node': 3.587.0
|
||||
'@smithy/config-resolver': 3.0.4
|
||||
'@smithy/core': 2.2.4
|
||||
'@smithy/fetch-http-handler': 3.2.0
|
||||
'@smithy/config-resolver': 3.0.5
|
||||
'@smithy/core': 2.2.6
|
||||
'@smithy/fetch-http-handler': 3.2.1
|
||||
'@smithy/hash-node': 3.0.3
|
||||
'@smithy/invalid-dependency': 3.0.3
|
||||
'@smithy/middleware-content-length': 3.0.3
|
||||
'@smithy/middleware-endpoint': 3.0.4
|
||||
'@smithy/middleware-retry': 3.0.7
|
||||
'@smithy/middleware-endpoint': 3.0.5
|
||||
'@smithy/middleware-retry': 3.0.9
|
||||
'@smithy/middleware-serde': 3.0.3
|
||||
'@smithy/middleware-stack': 3.0.3
|
||||
'@smithy/node-config-provider': 3.1.3
|
||||
'@smithy/node-http-handler': 3.1.1
|
||||
'@smithy/node-config-provider': 3.1.4
|
||||
'@smithy/node-http-handler': 3.1.2
|
||||
'@smithy/protocol-http': 4.0.3
|
||||
'@smithy/smithy-client': 3.1.5
|
||||
'@smithy/smithy-client': 3.1.7
|
||||
'@smithy/types': 3.3.0
|
||||
'@smithy/url-parser': 3.0.3
|
||||
'@smithy/util-base64': 3.0.0
|
||||
'@smithy/util-body-length-browser': 3.0.0
|
||||
'@smithy/util-body-length-node': 3.0.0
|
||||
'@smithy/util-defaults-mode-browser': 3.0.7
|
||||
'@smithy/util-defaults-mode-node': 3.0.7
|
||||
'@smithy/util-endpoints': 2.0.4
|
||||
'@smithy/util-defaults-mode-browser': 3.0.9
|
||||
'@smithy/util-defaults-mode-node': 3.0.9
|
||||
'@smithy/util-endpoints': 2.0.5
|
||||
'@smithy/util-middleware': 3.0.3
|
||||
'@smithy/util-retry': 3.0.3
|
||||
'@smithy/util-utf8': 3.0.0
|
||||
|
|
@ -17232,28 +17230,28 @@ snapshots:
|
|||
'@aws-sdk/util-endpoints': 3.587.0
|
||||
'@aws-sdk/util-user-agent-browser': 3.577.0
|
||||
'@aws-sdk/util-user-agent-node': 3.587.0
|
||||
'@smithy/config-resolver': 3.0.4
|
||||
'@smithy/core': 2.2.4
|
||||
'@smithy/fetch-http-handler': 3.2.0
|
||||
'@smithy/config-resolver': 3.0.5
|
||||
'@smithy/core': 2.2.6
|
||||
'@smithy/fetch-http-handler': 3.2.1
|
||||
'@smithy/hash-node': 3.0.3
|
||||
'@smithy/invalid-dependency': 3.0.3
|
||||
'@smithy/middleware-content-length': 3.0.3
|
||||
'@smithy/middleware-endpoint': 3.0.4
|
||||
'@smithy/middleware-retry': 3.0.7
|
||||
'@smithy/middleware-endpoint': 3.0.5
|
||||
'@smithy/middleware-retry': 3.0.9
|
||||
'@smithy/middleware-serde': 3.0.3
|
||||
'@smithy/middleware-stack': 3.0.3
|
||||
'@smithy/node-config-provider': 3.1.3
|
||||
'@smithy/node-http-handler': 3.1.1
|
||||
'@smithy/node-config-provider': 3.1.4
|
||||
'@smithy/node-http-handler': 3.1.2
|
||||
'@smithy/protocol-http': 4.0.3
|
||||
'@smithy/smithy-client': 3.1.5
|
||||
'@smithy/smithy-client': 3.1.7
|
||||
'@smithy/types': 3.3.0
|
||||
'@smithy/url-parser': 3.0.3
|
||||
'@smithy/util-base64': 3.0.0
|
||||
'@smithy/util-body-length-browser': 3.0.0
|
||||
'@smithy/util-body-length-node': 3.0.0
|
||||
'@smithy/util-defaults-mode-browser': 3.0.7
|
||||
'@smithy/util-defaults-mode-node': 3.0.7
|
||||
'@smithy/util-endpoints': 2.0.4
|
||||
'@smithy/util-defaults-mode-browser': 3.0.9
|
||||
'@smithy/util-defaults-mode-node': 3.0.9
|
||||
'@smithy/util-endpoints': 2.0.5
|
||||
'@smithy/util-middleware': 3.0.3
|
||||
'@smithy/util-retry': 3.0.3
|
||||
'@smithy/util-utf8': 3.0.0
|
||||
|
|
@ -18766,22 +18764,10 @@ snapshots:
|
|||
|
||||
'@babel/regjsgen@0.8.0': {}
|
||||
|
||||
'@babel/runtime@7.20.7':
|
||||
dependencies:
|
||||
regenerator-runtime: 0.13.11
|
||||
|
||||
'@babel/runtime@7.23.1':
|
||||
dependencies:
|
||||
regenerator-runtime: 0.14.1
|
||||
|
||||
'@babel/runtime@7.24.0':
|
||||
dependencies:
|
||||
regenerator-runtime: 0.14.1
|
||||
|
||||
'@babel/runtime@7.24.4':
|
||||
dependencies:
|
||||
regenerator-runtime: 0.14.1
|
||||
|
||||
'@babel/runtime@7.24.7':
|
||||
dependencies:
|
||||
regenerator-runtime: 0.14.1
|
||||
|
|
@ -19182,7 +19168,7 @@ snapshots:
|
|||
|
||||
'@emotion/react@11.10.5(@babel/core@7.24.7)(@types/react@18.3.3)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.7
|
||||
'@emotion/babel-plugin': 11.10.5(@babel/core@7.24.7)
|
||||
'@emotion/cache': 11.10.5
|
||||
'@emotion/serialize': 1.1.1
|
||||
|
|
@ -22699,7 +22685,7 @@ snapshots:
|
|||
|
||||
'@radix-ui/react-dialog@1.0.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.7
|
||||
'@radix-ui/primitive': 1.0.0
|
||||
'@radix-ui/react-compose-refs': 1.0.0(react@18.3.1)
|
||||
'@radix-ui/react-context': 1.0.0(react@18.3.1)
|
||||
|
|
@ -26237,7 +26223,7 @@ snapshots:
|
|||
css-what: 6.1.0
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.0.1
|
||||
domutils: 3.1.0
|
||||
|
||||
cheerio@1.0.0-rc.10:
|
||||
dependencies:
|
||||
|
|
@ -26254,7 +26240,7 @@ snapshots:
|
|||
cheerio-select: 2.1.0
|
||||
dom-serializer: 2.0.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.0.1
|
||||
domutils: 3.1.0
|
||||
htmlparser2: 8.0.2
|
||||
parse5: 7.1.2
|
||||
parse5-htmlparser2-tree-adapter: 7.0.0
|
||||
|
|
@ -26952,7 +26938,7 @@ snapshots:
|
|||
|
||||
date-fns@2.30.0:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.7
|
||||
|
||||
date-fns@3.6.0: {}
|
||||
|
||||
|
|
@ -27196,6 +27182,12 @@ snapshots:
|
|||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
|
||||
domutils@3.1.0:
|
||||
dependencies:
|
||||
dom-serializer: 2.0.0
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
|
||||
dot-case@3.0.4:
|
||||
dependencies:
|
||||
no-case: 3.0.4
|
||||
|
|
@ -27283,6 +27275,8 @@ snapshots:
|
|||
|
||||
entities@4.4.0: {}
|
||||
|
||||
entities@4.5.0: {}
|
||||
|
||||
env-ci@7.3.0:
|
||||
dependencies:
|
||||
execa: 5.1.1
|
||||
|
|
@ -29175,8 +29169,8 @@ snapshots:
|
|||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.0.1
|
||||
entities: 4.4.0
|
||||
domutils: 3.1.0
|
||||
entities: 4.5.0
|
||||
|
||||
http-cache-semantics@4.1.1: {}
|
||||
|
||||
|
|
@ -29913,7 +29907,7 @@ snapshots:
|
|||
object.assign: 4.1.5
|
||||
object.values: 1.1.7
|
||||
|
||||
juice@9.0.0(encoding@0.1.13):
|
||||
juice@9.1.0(encoding@0.1.13):
|
||||
dependencies:
|
||||
cheerio: 1.0.0-rc.12
|
||||
commander: 6.2.1
|
||||
|
|
@ -31217,7 +31211,7 @@ snapshots:
|
|||
|
||||
mjml-cli@4.14.0(encoding@0.1.13):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.0
|
||||
'@babel/runtime': 7.24.7
|
||||
chokidar: 3.5.3
|
||||
glob: 7.2.3
|
||||
html-minifier: 4.0.0
|
||||
|
|
@ -31241,12 +31235,12 @@ snapshots:
|
|||
|
||||
mjml-core@4.14.0(patch_hash=zxxsxbqejjmcwuzpigutzzq6wa)(encoding@0.1.13):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.0
|
||||
'@babel/runtime': 7.24.7
|
||||
cheerio: 1.0.0-rc.10
|
||||
detect-node: 2.0.4
|
||||
html-minifier: 4.0.0
|
||||
js-beautify: 1.14.6
|
||||
juice: 9.0.0(encoding@0.1.13)
|
||||
juice: 9.1.0(encoding@0.1.13)
|
||||
lodash: 4.17.21
|
||||
mjml-migrate: 4.14.0(encoding@0.1.13)
|
||||
mjml-parser-xml: 4.14.0
|
||||
|
|
@ -31352,7 +31346,7 @@ snapshots:
|
|||
|
||||
mjml-migrate@4.14.0(encoding@0.1.13):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.0
|
||||
'@babel/runtime': 7.24.7
|
||||
js-beautify: 1.14.6
|
||||
lodash: 4.17.21
|
||||
mjml-core: 4.14.0(patch_hash=zxxsxbqejjmcwuzpigutzzq6wa)(encoding@0.1.13)
|
||||
|
|
@ -31378,7 +31372,7 @@ snapshots:
|
|||
|
||||
mjml-preset-core@4.14.0(encoding@0.1.13):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.0
|
||||
'@babel/runtime': 7.24.7
|
||||
mjml-accordion: 4.14.0(encoding@0.1.13)
|
||||
mjml-body: 4.14.0(encoding@0.1.13)
|
||||
mjml-button: 4.14.0(encoding@0.1.13)
|
||||
|
|
@ -31457,7 +31451,7 @@ snapshots:
|
|||
|
||||
mjml-validator@4.13.0:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.0
|
||||
'@babel/runtime': 7.24.7
|
||||
|
||||
mjml-wrapper@4.14.0(encoding@0.1.13):
|
||||
dependencies:
|
||||
|
|
@ -31470,7 +31464,7 @@ snapshots:
|
|||
|
||||
mjml@4.14.0(encoding@0.1.13):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.7
|
||||
'@babel/runtime': 7.24.7
|
||||
mjml-cli: 4.14.0(encoding@0.1.13)
|
||||
mjml-core: 4.14.0(patch_hash=zxxsxbqejjmcwuzpigutzzq6wa)(encoding@0.1.13)
|
||||
mjml-migrate: 4.14.0(encoding@0.1.13)
|
||||
|
|
@ -33077,7 +33071,7 @@ snapshots:
|
|||
|
||||
react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.7
|
||||
dom-helpers: 5.2.1
|
||||
loose-envify: 1.4.0
|
||||
prop-types: 15.8.1
|
||||
|
|
@ -33229,8 +33223,6 @@ snapshots:
|
|||
|
||||
regenerate@1.4.2: {}
|
||||
|
||||
regenerator-runtime@0.13.11: {}
|
||||
|
||||
regenerator-runtime@0.14.1: {}
|
||||
|
||||
regenerator-transform@0.15.2:
|
||||
|
|
@ -35579,7 +35571,7 @@ snapshots:
|
|||
|
||||
yup@0.29.3:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
'@babel/runtime': 7.24.7
|
||||
fn-name: 3.0.0
|
||||
lodash: 4.17.21
|
||||
lodash-es: 4.17.21
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export function normalizeCliOutput(value: string) {
|
|||
.replace(/\x1B[[(?);]{0,2}(;?\d)*./g, '')
|
||||
.replace(
|
||||
/(http:\/\/localhost:\d+)\/([^\/]+)\/([^\/]+)\/([^\/]+)/,
|
||||
'$1/$organization/$project/$target',
|
||||
'$appUrl/$organization/$project/$target',
|
||||
)
|
||||
.replace(/history\/[$]*\w+-\w+-\w+-\w+-\w+/i, 'history/$version')
|
||||
.trim(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue