Skip to content

Commit

Permalink
feat: add graphql:execute (#218)
Browse files Browse the repository at this point in the history
* feat: add graphql:execute

Adds command to execute arbitrary graphql queries and mutations.

* chore: adds tests to graphql:execute command
  • Loading branch information
nathanielc authored Mar 18, 2024
1 parent a69ff88 commit 9405b98
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/cli/globalSetup.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const TEST_DAEMON_CONFIG = {
'admin-dids': [
'did:key:z6Mkh3VVZHjMWmBFkmixiYsZmJYAEkASk4ScjZDkawn6Npcu', // used in composites.test.ts
'did:key:z6MkpRhEWywReoFtQMQGqSmTu5mp9vQVok86Qha2sn6e32Db', // used in models.test.ts
'did:key:z6MkemC7PdH6hm6xJ764qN3xbWYyUfc8S2pgkDvYsBcLZUqw', // used in graphql.test.ts
],
'cors-allowed-origins': [new RegExp('.*')],
},
Expand Down
54 changes: 54 additions & 0 deletions packages/cli/src/commands/graphql/execute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Command, type CommandFlags } from '../../command.js'
import { Args } from '@oclif/core'
import { ComposeClient } from '@composedb/client'
import { RuntimeCompositeDefinition } from '@composedb/types'
import fs from 'fs-extra'

export default class Execute extends Command<
CommandFlags,
{ query: string; vars: Record<string, any> }

Check warning on line 9 in packages/cli/src/commands/graphql/execute.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test on Node 20 and macOS-latest

Unexpected any. Specify a different type
> {
static description = 'execute a GraphQL query or mutation'

static args = {
query: Args.string({
required: true,
description: 'GraphQL query or mutation',
}),
vars: Args.custom({
required: false,
description:
'variables as JSON to provide to a mutation. A "did" variable is always added that is the DID from the environment.',
parse: (input) => Promise.resolve(JSON.parse(input)),
})(),
}

static flags = {
...Command.flags,
runtimeDefinitionPath: Args.string({
required: false,
description: 'path to runtime-composite definition json file',
}),
}

async run(): Promise<void> {
this.spinner.start('Executing GraphQL...')
try {
const runtimeDefinitionPath =
(this.flags.runtimeDefinitionPath as string) || 'runtime-composite.json'
const runtimeDefinitionFile = await fs.readFile(runtimeDefinitionPath)

Check warning on line 39 in packages/cli/src/commands/graphql/execute.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test on Node 20 and macOS-latest

Caution: `fs` also has a named export `readFile`. Check if you meant to write `import {readFile} from 'fs-extra'` instead
const definition = JSON.parse(runtimeDefinitionFile.toString()) as RuntimeCompositeDefinition
const compose = new ComposeClient({ ceramic: this.ceramic, definition })
compose.setDID(this.authenticatedDID)
const vars = {
...this.args.vars,
did: compose.id,
}
this.log(JSON.stringify(await compose.executeQuery(this.args.query, vars), undefined, ' '))
this.spinner.succeed(`Executing GraphQL... Done!`)
} catch (e) {
console.log((e as Error).stack)
this.spinner.fail((e as Error).message)
}
}
}
89 changes: 88 additions & 1 deletion packages/cli/test/graphql.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { execa } from 'execa'
import { execa, ExecaChildProcess } from 'execa'

describe('graphql', () => {
describe('graphql:schema', () => {
Expand Down Expand Up @@ -92,4 +92,91 @@ describe('graphql', () => {
}
}, 60000)
})
describe('graphql:execute', () => {
const seed = 'bd019adffe40658f2261bfe72589fdf6b57727fa3878574d4202c5776bb8b357'
test('graphql execute fails without query param', async () => {
try {
await execa('bin/run.js', ['graphql:execute'])
// The command should have failed and not gotten to this line
expect(false).toBe(true)
} catch (e) {
const proc = e as ExecaChildProcess
// In the case of execa the thrown error is actually the process.
// Allow conditional expect as we need to further expect properties of the thrown error.

// eslint-disable-next-line jest/no-conditional-expect
expect(proc.exitCode).not.toBe(0)
// eslint-disable-next-line jest/no-conditional-expect
expect(proc.stderr?.toString().includes('GraphQL query or mutation')).toBe(true)
}
}, 60000)

test('graphql execute runs', async () => {
// First deploy the composite
await execa('bin/run.js', [
'composite:deploy',
'test/mocks/encoded.composite.picture.post.json',
`--did-private-key=${seed}`,
])

// Create a post
const mutation = await execa('bin/run.js', [
'graphql:execute',
'--runtimeDefinitionPath',
'test/mocks/runtime.composite.picture.post.json',
`--did-private-key=${seed}`,
`
mutation CreatePost($i: CreatePostInput!) {
createPost(input: $i) {
document {
text
}
}
}
`,
JSON.stringify({ i: { content: { text: 'test post' } } }),
])
expect(mutation.stdout.toString()).toBe(`{
"data": {
"createPost": {
"document": {
"text": "test post"
}
}
}
}`)

// Query the first post
const query = await execa('bin/run.js', [
'graphql:execute',
'--runtimeDefinitionPath',
'test/mocks/runtime.composite.picture.post.json',
`--did-private-key=${seed}`,
`
query GetPosts {
postIndex(first: 1) {
edges {
node {
text
}
}
}
}
`,
])
expect(query.stdout.toString()).toBe(`{
"data": {
"postIndex": {
"edges": [
{
"node": {
"text": "test post"
}
}
]
}
}
}`)
}, 60000)
})
})

0 comments on commit 9405b98

Please sign in to comment.