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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions controlplane/src/core/repositories/SubgraphRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,7 @@ export class SubgraphRepository {
const federatedGraphDTOs = await fedGraphRepo.bySubgraphLabels({
labels: baseSubgraph[0].labels?.map?.((l) => splitLabel(l)) ?? [],
namespaceId: data.namespaceId,
excludeContracts: true,
});

for (const federatedGraphDTO of federatedGraphDTOs) {
Expand Down Expand Up @@ -622,13 +623,10 @@ export class SubgraphRepository {
const affectedGraphs = await fedGraphRepo.bySubgraphLabels({
labels: subgraph.labels,
namespaceId: data.namespaceId,
excludeContracts: true,
});

for (const graph of affectedGraphs) {
if (graph.contract) {
continue;
}

// If the subgraph has changed, always trigger composition
if (affectedFederatedGraphById.has(graph.id) && !subgraphChanged) {
/** If the federated graph matches the old labels AND the new labels,
Expand Down
78 changes: 78 additions & 0 deletions controlplane/test/contracts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb
import { joinLabel } from '@wundergraph/cosmo-shared';
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
import { RouterConfig } from '@wundergraph/cosmo-connect/dist/node/v1/node_pb';
import { Label } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb';
import { normalizeString } from '../../composition/tests/utils/utils.js';
import { afterAllSetup, beforeAllSetup, genID, genUniqueLabel } from '../src/core/test-util.js';
import { unsuccessfulBaseCompositionError } from '../src/core/errors/errors.js';
Expand All @@ -12,17 +13,21 @@ import {
assertFeatureFlagExecutionConfig,
assertNumberOfCompositions,
createAndPublishSubgraph,
createFeatureFlag,
createFederatedGraph,
createNamespace,
createThenPublishSubgraph,
DEFAULT_NAMESPACE,
DEFAULT_ROUTER_URL,
DEFAULT_SUBGRAPH_URL_ONE,
DEFAULT_SUBGRAPH_URL_TWO,
featureFlagIntegrationTestSetUp,
getDebugTestOptions,
SetupTest,
} from './test-util.js';

const schemaDefinition = `schema {\n query: Query\n}\n\n`;
const isDebugMode = false;
let dbname = '';

vi.mock('../src/core/clickhouse/index.js', () => {
Expand Down Expand Up @@ -2228,4 +2233,77 @@ describe('Contract tests', () => {
`),
);
});

test(
'that a contract is not composed multiple times when a feature subgraph is published',
getDebugTestOptions(isDebugMode),
async (testContext) => {
const { client, server } = await SetupTest({ dbname, chClient });
testContext.onTestFinished(() => server.close());

const namespace = genID('namespace').toLowerCase();
const labels: Label[] = [];
const baseGraphName = genID('baseFederatedGraph');
const contractName = genID('contract');
const ffName = genID('featureFlag');

await createNamespace(client, namespace);
await featureFlagIntegrationTestSetUp(
client,
[
{ name: 'users', hasFeatureSubgraph: true },
{ name: 'products-standalone', hasFeatureSubgraph: true },
],
baseGraphName,
labels,
namespace,
);

// Create a contract
const createContractResponse = await client.createContract({
name: contractName,
namespace,
sourceGraphName: baseGraphName,
excludeTags: [],
includeTags: [],
routingUrl: 'http://localhost:8081',
readme: 'test',
});

expect(createContractResponse.response?.code).toBe(EnumStatusCode.OK);

// Create a feature flag
await createFeatureFlag(client, ffName, labels, ['products-standalone-feature'], namespace, true);

/**
* We expect the base graph and contract to be composed twice, once for the creation and once when the
* feature flag was created
*/
await assertNumberOfCompositions(client, baseGraphName, 2, namespace, EnumStatusCode.OK, true);
await assertNumberOfCompositions(client, contractName, 2, namespace, EnumStatusCode.OK, true);

// We expect to see feature flag to be composed
await assertNumberOfCompositions(client, baseGraphName, 3, namespace);
await assertNumberOfCompositions(client, contractName, 3, namespace);

// Update the feature subgraph
const updateFeatureSubgraphResp = await client.publishFederatedSubgraph({
name: 'products-standalone-feature',
namespace,
schema: fs
.readFileSync(join(process.cwd(), `test/test-data/feature-flags/products-feature-update.graphql`))
.toString(),
});

expect(updateFeatureSubgraphResp.response?.code).toBe(EnumStatusCode.OK);

// We expect to see a new composition for both the base graph and contract
await assertNumberOfCompositions(client, baseGraphName, 3, namespace, EnumStatusCode.OK, true);
await assertNumberOfCompositions(client, contractName, 3, namespace, EnumStatusCode.OK, true);

// And also expect a new composition for the feature flag
await assertNumberOfCompositions(client, baseGraphName, 5, namespace);
await assertNumberOfCompositions(client, contractName, 5, namespace);
},
);
});
Loading