README.md

# Absinthe Query All

Absinthe Query All is a tool for comprehensively and automatically querying and testing your Absinthe GraphQL schema.

You can read the story of how the core functionality of this package was developed [here](https://www.neurodynamic.online/writing/frontend-to-backend-type-safety).

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `absinthe_query_all` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:absinthe_query_all, "~> 0.4.0"}
  ]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at <https://hexdocs.pm/absinthe_query_all>.

## How to use

This core AbsintheQueryAll module provides two utility functions.

### the `get_all_operations` function

This function returns a list of every operation of the specified type (i.e. query or mutation) in your absinthe graphql schema.

```elixir
AbsintheQueryAll.get_all_operations(SomeAbsintheSchema, :query)
[
  :someQuery, :someOtherQuery, :someThirdQuery
]
```
### the `get_comprehensive_response` function

The `get_comprehensive_response` function runs the graphql operation specified, requesting a response that contains every possible field and subfield.

```elixir
AbsintheQueryAll.get_comprehensive_response(%{
  schema: SomeAbsintheSchema,
  operation_type: :query,
  operation_name: :someOperation,
})
{:ok, %{data: %{"someField" => 1, "someOtherField" => 2, "someThirdField" => 3}}}
```

### Automatic comprehensiveness checking of your absinthe `resolve` callbacks

Why these two functions? So that you can set them up like this:

```elixir
describe "SomeAbsintheSchema" do
  # First we get every single query available in the schema
  for operation <- AbsintheQueryAll.get_all_operations(SomeAbsintheSchema, :query) do
    # Then we create a test case
    test "query `#{operation}` responds successfully" do
      # Inside the test case, we run the query
      {status, response} = AbsintheQueryAll.get_comprehensive_response(%{
        schema: TestAbsintheSchema,
        operation_type: :query,
        operation_name: unquote(Macro.escape(operation)),
      })

      # And then we check to see that it returned successfully
      assert status == :ok
      assert Map.has_key?(response, :data)
      refute Map.has_key?(response, :errors)
    end
  end

  # Then we do the same thing with our mutations
  for operation <- AbsintheQueryAll.get_all_operations(SomeAbsintheSchema, :mutation) do
    test "mutation `#{operation}` responds successfully" do
      {status, response} = AbsintheQueryAll.get_comprehensive_response(%{
        schema: TestAbsintheSchema,
        operation_type: :mutation,
        operation_name: unquote(Macro.escape(operation)),
      })

      assert status == :ok
      assert Map.has_key?(response, :data)
      refute Map.has_key?(response, :errors)
    end
  end
end
```

With that setup, your app will automatically generate a test for every single graphql operation and make sure that a response is being generated for every single field in that operation. Just a nice, simple extra layer protection against a specific type of bug.

### Optional arguments to `AbsintheQueryAll.get_comprehensive_response` and `Query.comprehensive`

#### Generating Arguments

Your schema likely has operations that require arguments. In order to generate arguments to pass to those operations, the `get_comprehensive_response` function has an optional argument, `argument_generator`, that you can pass an argument generating function to. The argument generating function should take two arguments: the first is the `operation_name` (e.g. `:someQuery`) and the second is the name of the argument you're generating (as an atom; e.g. `:userId`).

For example, you might call `get_comprehensive_response` like this:

```elixir
AbsintheQueryAll.get_comprehensive_response(%{
  schema: SomeAbsintheSchema,
  operation_type: :query,
  operation_name: :testQuery,
  argument_generator: fn operation_name, argument_name ->
    case {operation_name, argument_name} do
      {:someQuery, :theNameOfSomeStringArgumentToTheQuery} ->
        # if this argument requires a string, here's where you choose what string it gets
        "some string"
      {:someQuery, :theNameOfSomeIntegerArgumentToTheQuery} ->
        7

    end 
})
```

#### Passing in Context

The optional `context` argument on `get_comprehensive_response` is just for passing along the `context` map that `Absinthe.run` takes. You might use this for something like passing in information on the currently logged in user if that has an effect on what your graphql endpoint will return for some operations.

```elixir
AbsintheQueryAll.get_comprehensive_response(%{
  schema: SomeAbsintheSchema,
  operation_type: :query,
  operation_name: :testQuery,
  context: %{current_user: user}
})
```

#### Restricting fields in the response

If you would like a response that *does not* contain every possible field, you can pass the permitted_fields option to `get_comprehensive_response`. This option should be a map. It is a map instead of a list so that it can facilitate nesting of the permitted fields specification.

For example, the following code

```elixir
AbsintheQueryAll.get_comprehensive_response(%{
  schema: SomeAbsintheSchema,
  operation_type: :query,
  operation_name: :testQuery,
  permitted_fields: %{someField: nil, someOtherField: %{someNestedField: nil}}
})
"query testQuery {\n  testQuery { someField someOtherField { someNestedField } }\n}\n"
```

presumes that `someOtherField` is an object with fields of its own, and will generate a query that only requests the `someField` and `someOtherField` fields at the top level, and only the `someNestedField` field inside the `someOtherField` object.

#### Aliasing Options

The option to pass `parent_alias_generator` and `leaf_alias_generator` functions will most likely not be useful to most people, unless you, (A) specifically need to test something involving aliases, or (B) are using the utility functions available in this package to interface with tools like [elm-graphql](https://github.com/dillonkearns/elm-graphql) that automatically alias fields. But they are there if you need them.

```elixir
iex> Query.comprehensive(%{
...>   schema: SomeAbsintheSchema,
...>   operation_type: :query,
...>   operation_name: :testQuery,
...>   parent_alias_generator: fn _field_name, _field_args -> "someAlias" end
...> })
"query testQuery {\n  someAlias: testQuery { someField }\n}\n"
```

```elixir
iex> Query.comprehensive(%{
...>   schema: SomeAbsintheSchema,
...>   operation_type: :query,
...>   operation_name: :testQuery,
...>   leaf_alias_generator: fn field_name, _field_details -> "#{field_name}Alias" end
...> })
"query testQuery {\n  testQuery { someFieldAlias: someField }\n}\n"
```

## Other possibilities

This library intentionally exposes a lot of the underlying modules/functions that make the two main functions work, in case anyone wants to use them to build other tools. I use them for a variety of nice graphql testing functions that I may take a stab at generalizing and open-sourcing in the future if I find the time.