Tests con RSpec para autenticación de tokens

Clase 21 de 33Curso de Creación de APIs con Ruby on Rails

Resumen

Implementar autenticación con tokens en un API requiere pruebas enfocadas y repetibles. Aquí se construye, con Test Driven Development, una suite en RSpec que valida el acceso a posts según su visibilidad y la identidad del usuario, usando headers de Authorization, context y subject para asegurar un payload y response correctos.

¿Qué objetivo tiene la autenticación con tokens en el API?

El objetivo es crear una implementación propia basada en tokens y empezar por las pruebas. Se separa el alcance en tres endpoints: detalle, creación y actualización. Se inicia por el de detalle, que resulta más directo. Para organizar, se crea un nuevo archivo de pruebas dedicado a posts con autenticación y se aprovecha context para describir escenarios.

En el flujo planteado se prioriza: - Empezar por pruebas y dejar que fallen primero. - Aislar el recurso de posts que requiere autenticación. - Dividir por endpoints y por visibilidad del post: público o borrador. - Probar acceso a contenido de otro usuario y del mismo usuario.

¿Cómo se estructura la suite de pruebas con RSpec?

Se organiza el spec con bloques describe y context para expresar intención. Se usan let para datos de prueba, subject para enfocar las expectativas en response y payload, y se simulan requests con get hacia el endpoint de detalle.

¿Cómo se preparan los datos con factory bot y let?

Se generan dos usuarios y al menos un post por cada uno. En los posts se referencia explícitamente el user_id para diferenciar autorías. Esto permite cubrir el caso de “post de otro autor” y “post del mismo usuario”.

# private_posts_spec.rb
RSpec.describe 'Posts with authentication', type: :request do
  describe 'GET /posts/:id' do
    context 'with authentication' do
      let(:user)        { create(:user) }
      let(:other_user)  { create(:user) }

      let(:user_post)        { create(:post, user_id: user.id) }
      let(:other_user_post)  { create(:post, user_id: other_user.id) }

      # Variante publicada y borrador del post de otro autor.
      let(:public_post) { other_user_post }     # aquí garantiza que esté publicado.
      let(:draft_post)  { other_user_post }     # aquí configura que sea borrador.

      # ...
    end
  end
end

¿Cómo se modelan los headers de autenticación en HTTP?

La autenticación viaja en un header adicional: Authorization. Por convención, el valor comienza con "bitter" seguido del token. Se define como un hash para pasarlo al request y se crean variantes para cada usuario.

let(:auth_headers)       { { 'Authorization' => "bitter #{user.auth_token}" } }
let(:other_auth_headers) { { 'Authorization' => "bitter #{other_user.auth_token}" } }

Esta convención sigue lo usado en OAuth y permite enviar el token en cada request autenticado.

¿Cómo se usan context y subject para validar response y payload?

Se declara un subject para el response y otro para el payload. Así, las expectativas quedan expresivas y centradas en el objeto bajo prueba.

subject(:response) { super() }
subject(:payload)  { JSON.parse(response.body) }

context 'when asking for other author post' do
  context 'when post is public' do
    before { get "/posts/#{public_post.id}", headers: auth_headers }

    it 'incluye el id en el payload' do
      expect(payload).to include('id' => public_post.id)
    end

    it 'responde con status ok' do
      expect(response).to have_http_status(:ok)
    end
  end

  context 'when post is draft' do
    before { get "/posts/#{draft_post.id}", headers: auth_headers }

    it 'incluye un error en el payload' do
      expect(payload).to include('error')
    end

    it 'responde con not found' do
      expect(response).to have_http_status(:not_found)
    end
  end
end

¿Qué comportamientos se validan en el endpoint de detalle?

Se cubren escenarios clave para restringir el acceso cuando corresponde y permitirlo cuando procede. La petición se realiza con get al endpoint posts/:id usando headers de autenticación válidos.

  • Post público de otro autor: el payload incluye el id.
  • Post público de otro autor: el response tiene status ok.
  • Post en borrador de otro autor: el payload incluye un error.
  • Post en borrador de otro autor: el response devuelve not found.
  • Se ejecutan las pruebas y fallan al inicio: falta implementar la lógica de autenticación.

Así, la suite define un contrato verificable antes de escribir la lógica. En el siguiente paso, se implementa la autenticación para que todas las expectativas pasen.

¿Agregarías más casos, como acceso al post del mismo usuario o creación y actualización? Deja tus ideas y dudas en los comentarios.