Как протестировать запрос GraphQL, предоставляющий токен авторизации с сервером Apollo?

Документация по тестированию интеграции с сервером Apollo показывает, как тестировать простые запросы с помощью createTestClient:

const { query } = createTestClient(server);
const res = await query({ query: GET_LAUNCH, variables: { id: 1 } });

Пытаясь протестировать запрос, который требует токена авторизации, я попытался передать jwtToken в качестве дополнительного поля объекту, переданному в запрос, как показано ниже:

const res = await query({ 
  query: MY_QUERY, 
  http: { headers: { authorization: `Bearer ${jwtToken}` } },
});

К сожалению, это не работает. При попытке запустить получаю следующий ответ:

{
  "http": {
    "headers": {}
  },
  "errors": [{
    "message": "must authenticate",
    "locations": [{
      "line": 2,
      "column": 3
    }],
    "path": ["myQuery"],
    "extensions": {
      "code": "UNAUTHENTICATED"
    }
  }],
  "data": {
    "myQuery": null
  }
}

Любая идея, как правильно тестировать запросы, требующие токена авторизации?

С использованием:

  "devDependencies": {
    "jest": "^23.6.0",
    "apollo-server-testing": "^2.4.8"
  }
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
9
0
4 186
3

Ответы 3

Вы можете создать новый экземпляр сервера с отдельным контекстом для своих тестов. Что-то вроде этого (может быть, не на 100% правильно синтаксически, но вы поймете идею):

// Create a test user, no need to mess with JWT here, just create a plain object
// that resembles a user in your system.
const testUser = {
  id: 'testId',
  email: '[email protected]',
};

// Use this for both test servers and real servers
export function getContext(models, viewer) {
  return {
    ...models,
    viewer,
  };
}

// Create test server
const testServer = new ApolloServer({
  typeDefs,
  resolvers,
  context: getContext({}, testUser),
});

const { query } = createTestClient(testServer);

// The actual server (you know better how this looks on your end, but just for
// the concept)
const realServer = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req, res }) => {
    const user = decodeUser(req.headers.authorization);

    return getContext({}, user);
  },
});

Решение Микаэля у меня сработал. Чтобы расширить это, я смог импортировать тот же самый ApolloServer, который я использую, в основной файл сервера app.js, а затем перезаписать свойство контекста в тесте, чтобы добавить требуемый токен носителя.

// apollo-server.js
const { ApolloServer } = require('apollo-server-express');

const resolvers = require('./resolvers');
const typeDefs = require('./schema');

const apolloServer = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => ({ bearerToken: req.headers.authorization }),
});

module.exports = apolloServer;

Тогда перезапись контекста в моем тесте выглядела так.

// graphql.test.js
const apolloServer = require('./apollo-server');

apolloServer.context = () => ({ bearerToken: `Bearer <token>` });

const { query } = createTestClient(apolloServer);

const QUERY = gql`
  // ...
`;

const response = await query({ query: QUERY });
expect(response.data).eql({...})

Куда вы поместите свой процесс аутентификации, если ваш контекст содержит только то, как получить токен, где бы он ни находился?

Emmanuel Hadoux 14.01.2020 11:30

Это не аутентифицирует входящий запрос. Цель здесь — установить заголовок авторизации в запросе, который мы отправляем из GraphQL API во внешний REST API (или другой источник данных). В моем примере я использую тот же токен из запроса, который я получаю, и передаю его в запросе, который я отправляю во внешний REST API. Я надеюсь, что это поможет сделать это немного более ясным.

Calaway 15.01.2020 17:10

Мой вопрос должен был быть более ясным, но вы все равно ответили на него. Моя точка зрения заключалась в том, что если контекст имеет дело только с извлечением, аутентификация будет в каждом распознавателе, что является проблемой. Но учитывая, что у вас есть внешний API, я предполагаю, что вы имеете дело с ним в промежуточных слоях этого API.

Emmanuel Hadoux 16.01.2020 12:53

это не решает проблему, он хочет передать токен в запросе на написание интеграционного теста без издевательства над данными

Rafael Zeffa 10.12.2020 19:20

Я столкнулся с той же проблемой и нашел эту библиотеку:

https://github.com/zapier/apollo-server-integration-testing

Из документов: Почему бы не использовать тестирование сервера apollo? Вы не можете написать настоящие интеграционные тесты с помощью apollo-server-testing, потому что он не поддерживает серверы, которые полагаются на опцию контекста, являющуюся функцией, которая использует объект req.

Другие вопросы по теме