Skip to content

fix(policy): update graphql-eslint to v4, add more tests #6657

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
22 changes: 10 additions & 12 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,6 @@ const guildConfig = require('@theguild/eslint-config/base');
const { REACT_RESTRICTED_SYNTAX, RESTRICTED_SYNTAX } = require('@theguild/eslint-config/constants');
const path = require('path');

const SCHEMA_PATH = './packages/services/api/src/modules/*/module.graphql.ts';
const OPERATIONS_PATHS = [
'./packages/web/app/**/*.ts',
'./packages/web/app/**/*.tsx',
'./packages/web/app/**/*.graphql',
];

const rulesToExtends = Object.fromEntries(
Object.entries(guildConfig.rules).filter(([key]) =>
[
Expand Down Expand Up @@ -70,20 +63,25 @@ module.exports = {
parser: '@graphql-eslint/eslint-plugin',
plugins: ['@graphql-eslint'],
parserOptions: {
schema: SCHEMA_PATH,
operations: OPERATIONS_PATHS,
graphQLConfig: {
schema: './packages/services/api/src/modules/*/module.graphql.ts',
documents: [
'./packages/web/app/**/*.ts',
'./packages/web/app/**/*.tsx',
'./packages/web/app/**/*.graphql',
],
},
},
},
{
// Setup processor for operations/fragments definitions on code-files
files: ['packages/web/app/**/*.tsx', 'packages/web/app/**/*.ts'],
files: ['packages/web/app/**/*.{ts,tsx}'],
processor: '@graphql-eslint/graphql',
},
{
files: ['packages/web/app/**/*.graphql'],
plugins: ['@graphql-eslint'],
rules: {
'@graphql-eslint/require-id-when-available': 'error',
'@graphql-eslint/require-selections': 'error',
'@graphql-eslint/no-deprecated': 'error',
},
},
Expand Down
161 changes: 161 additions & 0 deletions integration-tests/tests/api/policy/policy-check.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,167 @@ export const createPolicy = (level: RuleInstanceSeverityLevel): SchemaPolicyInpu

describe('Schema policy checks', () => {
describe('model: composite', () => {
it('no-unreachable-types issue: directives are not scanned/marked as unused', async () => {
const policyObject: SchemaPolicyInput = {
rules: [
{
ruleId: 'no-unreachable-types',
severity: RuleInstanceSeverityLevel.Error,
},
],
};

const { tokens, policy } = await prepareProject(ProjectType.Federation);
await policy.setOrganizationSchemaPolicy(policyObject, true);
const cli = createCLI(tokens.registry);

await cli.publish({
sdl: /* GraphQL */ `
type Query {
a: String!
}
`,
serviceName: 'a',
serviceUrl: 'https://api.com/a',
expect: 'latest-composable',
});

await cli.publish({
sdl: /* GraphQL */ `
type Query {
b: String!
}
`,
serviceName: 'b',
serviceUrl: 'https://api.com/b',
expect: 'latest-composable',
});

// In this example, the policy checks sees the "hasRole" directive in the schema
// because we are using composeDirective.
const rawMessage = await cli.check({
sdl: /* GraphQL */ `
extend schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@composeDirective"])
@link(url: "https://myspecs.dev/myDirective/v1.0", import: ["@hasRole"])
@composeDirective(name: "@hasRole")

scalar Unused

scalar Used

scalar UsedInInput

directive @hasRole(role: Role!) on QUERY | MUTATION | FIELD_DEFINITION

enum Role {
admin
user
}

enum Permission {
read
write
create
delete
}

type Query {
userRole(roleID: Int!): UserRole! @hasRole(role: admin)
scalar(input: UsedInInput!): Used
}

type UserRole {
id: ID!
name: String!
}
`,
serviceName: 'c',
expect: 'rejected',
});
const message = stripAnsi(rawMessage);

expect(message).toContain(`Detected 2 errors`);
expect(message.split('\n').slice(1)).toEqual([
'✖ Detected 2 errors',
'',
' - Scalar type `Unused` is unreachable. (source: policy-no-unreachable-types)',
' - Enum type `Permission` is unreachable. (source: policy-no-unreachable-types)',
'',
'ℹ Detected 9 changes',
'',
' Safe changes:',
' - Type Permission was added',
' - Type Role was added',
' - Type Unused was added',
' - Type Used was added',
' - Type UsedInInput was added',
' - Type UserRole was added',
' - Field scalar was added to object type Query',
' - Field userRole was added to object type Query',
' - Directive hasRole was added',
'',
'View full report:',
expect.any(String),
'',
]);

// But in this one, we are not using composeDirective, so the final compose directive
// is not visible by the policy checker, and the policy checker will not detect it.
// This is why it's being reported an unused, and also other related inputs/types.
const rawMessage2 = await cli.check({
sdl: /* GraphQL */ `
scalar Unused

scalar Used

scalar UsedInInput

directive @hasRole(role: Role!) on QUERY | MUTATION | FIELD_DEFINITION

enum Role {
admin
user
}

enum Permission {
read
write
create
delete
}

type Query {
userRole(roleID: Int!): UserRole! @hasRole(role: admin)
scalar(input: UsedInInput!): Used
}

type UserRole {
id: ID!
name: String!
}
`,
serviceName: 'c',
expect: 'rejected',
});
const message2 = stripAnsi(rawMessage2);

expect(message2).toContain(`Detected 4 errors`);
expect(message2).toContain(
`Scalar type \`Unused\` is unreachable. (source: policy-no-unreachable-types)`,
);
expect(message2).toContain(
`Directive \`hasRole\` is unreachable. (source: policy-no-unreachable-types)`,
);
expect(message2).toContain(
`Enum type \`Role\` is unreachable. (source: policy-no-unreachable-types)`,
);
expect(message2).toContain(
`Enum type \`Permission\` is unreachable. (source: policy-no-unreachable-types)`,
);
});

it('Federation project with policy with only warnings, should check only the part that was changed', async () => {
const { tokens, policy } = await prepareProject(ProjectType.Federation);
await policy.setOrganizationSchemaPolicy(
Expand Down
Loading
Loading