diff --git a/spec/controllers/avaliacao_controller_spec.rb b/spec/controllers/avaliacao_controller_spec.rb new file mode 100644 index 0000000000..17406c311d --- /dev/null +++ b/spec/controllers/avaliacao_controller_spec.rb @@ -0,0 +1,202 @@ +require 'rails_helper' + +RSpec.describe AvaliacoesController, type: :controller do + let(:valid_question_type) { "texto_curto" } + + def create_turma + Turma.create!( + codigo: "ENG-#{rand(100..999)}", + nome: "Engenharia de Software", + semestre: "2025/1" + ) + end + + def create_template_padrao + modelo = Modelo.new(titulo: "Template Padrão", ativo: true) + modelo.perguntas.build(enunciado: "Pergunta Padrão", tipo: valid_question_type, opcoes: []) + modelo.save! + modelo + end + + def create_modelo_generico + modelo = Modelo.new(titulo: "Outro Modelo", ativo: true) + modelo.perguntas.build(enunciado: "Questão 1", tipo: valid_question_type, opcoes: []) + modelo.save! + modelo + end + + def create_avaliacao(turma, modelo) + Avaliacao.create!( + turma: turma, + modelo: modelo, + data_inicio: Time.current, + data_fim: 7.days.from_now + ) + end + + def stub_current_user(admin: false, turmas: []) + user = double("User", eh_admin?: admin, id: 1) + + allow(controller).to receive(:current_user).and_return(user) + + session_double = double("Session", user: user) + allow(Current).to receive(:session).and_return(session_double) + allow(Current).to receive(:user).and_return(user) + + unless admin + ids = turmas.map(&:id) + relation = Turma.where(id: ids) + allow(user).to receive(:turmas).and_return(relation) + end + + user + end + + + describe "GET #index" do + context "quando o usuário NÃO está logado" do + before do + allow(controller).to receive(:current_user).and_return(nil) + allow(Current).to receive(:session).and_return(nil) + end + + it "redireciona para o login" do + get :index + expect(response).to redirect_to(new_session_path) + end + end + + context "quando o usuário é ADMIN" do + before { stub_current_user(admin: true) } + + it "retorna sucesso (200 OK)" do + get :index + expect(response).to be_successful + end + end + + context "quando o usuário é ALUNO" do + let(:turma) { create_turma } + + before do + stub_current_user(admin: false, turmas: [ turma ]) + end + + it "retorna sucesso (200 OK)" do + get :index + expect(response).to be_successful + end + end + end + + describe "GET #gestao_envios" do + before { stub_current_user(admin: true) } + + it "retorna sucesso e carrega a página" do + get :gestao_envios + expect(response).to be_successful + end + end + + describe "POST #create" do + before { stub_current_user(admin: true) } + let!(:turma) { create_turma } + + context "Cenários de Falha" do + it "redireciona com alerta se a Turma não for encontrada" do + post :create, params: { turma_id: 0 } + expect(response).to redirect_to(gestao_envios_avaliacoes_path) + expect(flash[:alert]).to eq("Turma não encontrada.") + end + + it "redireciona com alerta se o Template Padrão não existir" do + Modelo.where(titulo: "Template Padrão").destroy_all + create_modelo_generico + + post :create, params: { turma_id: turma.id } + expect(response).to redirect_to(gestao_envios_avaliacoes_path) + expect(flash[:alert]).to include("Template Padrão não encontrado") + end + end + + context "Cenário de Sucesso" do + before { create_template_padrao } + + it "cria uma nova avaliação no banco" do + expect { + post :create, params: { turma_id: turma.id, data_fim: 5.days.from_now } + }.to change(Avaliacao, :count).by(1) + end + + it "redireciona com mensagem de sucesso" do + post :create, params: { turma_id: turma.id } + expect(response).to redirect_to(gestao_envios_avaliacoes_path) + expect(flash[:notice]).to include("Avaliação criada com sucesso") + end + + it "define data_fim padrão se não fornecida" do + post :create, params: { turma_id: turma.id, data_fim: "" } + avaliacao = Avaliacao.last + expect(avaliacao.data_fim).to be_within(1.second).of(7.days.from_now) + end + end + + context "Erro de Persistência" do + before { create_template_padrao } + + it "redireciona com alerta se o save falhar" do + allow_any_instance_of(Avaliacao).to receive(:save).and_return(false) + allow_any_instance_of(Avaliacao).to receive_message_chain(:errors, :full_messages).and_return([ "Erro no Banco" ]) + + post :create, params: { turma_id: turma.id } + expect(response).to redirect_to(gestao_envios_avaliacoes_path) + expect(flash[:alert]).to include("Erro no Banco") + end + end + end + + describe "GET #resultados" do + before { stub_current_user(admin: true) } + + let!(:turma) { create_turma } + let!(:modelo) { create_modelo_generico } + let!(:avaliacao) { create_avaliacao(turma, modelo) } + + context "Formato HTML" do + it "responde com sucesso (200 OK)" do + get :resultados, params: { id: avaliacao.id } + expect(response).to be_successful + end + + it "lida com erro de banco de dados (StatementInvalid) sem quebrar" do + allow(Avaliacao).to receive(:find).with(avaliacao.id.to_s).and_return(avaliacao) + allow(avaliacao).to receive(:submissoes).and_raise(ActiveRecord::StatementInvalid) + + get :resultados, params: { id: avaliacao.id } + + expect(flash[:alert]).to eq("Erro ao carregar submissões.") + expect(response).to be_successful + end + + it "executa a lógica de estatísticas sem erro (Happy Path)" do + get :resultados, params: { id: avaliacao.id } + expect(response).to be_successful + end + end + + context "Formato CSV" do + it "envia o arquivo gerado pelo service" do + csv_dummy_content = "Questao,Resposta\n1,Teste" + service_double = instance_double("CsvFormatterService") + + expect(CsvFormatterService).to receive(:new).with(avaliacao).and_return(service_double) + expect(service_double).to receive(:generate).and_return(csv_dummy_content) + + get :resultados, params: { id: avaliacao.id }, format: :csv + + expect(response.header['Content-Type']).to include('text/csv') + expect(response.body).to eq(csv_dummy_content) + end + end + end +end diff --git a/spec/controllers/modelos_controller_spec.rb b/spec/controllers/modelos_controller_spec.rb new file mode 100644 index 0000000000..3050eae6d3 --- /dev/null +++ b/spec/controllers/modelos_controller_spec.rb @@ -0,0 +1,231 @@ +require 'rails_helper' + +RSpec.describe ModelosController, type: :controller do + let(:valid_question_type) { "texto_curto" } + + let(:valid_attributes) { + { + titulo: "Modelo Válido", + ativo: true, + perguntas_attributes: [ + { + enunciado: "Qual o seu nome?", + tipo: valid_question_type, + opcoes: [] + } + ] + } + } + + let(:invalid_attributes) { + { titulo: "", ativo: true } + } + + def create_modelo(attributes = {}) + base_params = { titulo: "Modelo Persistido", ativo: true }.merge(attributes.except(:perguntas_attributes)) + modelo = Modelo.new(base_params) + + if modelo.perguntas.empty? + modelo.perguntas.build( + enunciado: "Pergunta Obrigatória", + tipo: valid_question_type, + opcoes: [] + ) + end + + modelo.save! + modelo + end + + before do + user_admin = double("User", eh_admin?: true) + session_obj = double("Session", user: user_admin) + allow(Current).to receive(:session).and_return(session_obj) + end + + describe "Verificação de Permissão" do + context "quando o usuário não é admin" do + before do + user_common = double("User", eh_admin?: false) + session_obj = double("Session", user: user_common) + allow(Current).to receive(:session).and_return(session_obj) + end + + it "redireciona para root_path" do + get :index + expect(response).to redirect_to(root_path) + expect(flash[:alert]).to eq("Acesso restrito a administradores.") + end + end + end + + describe "GET #index" do + it "retorna resposta de sucesso (200 OK)" do + create_modelo + get :index + expect(response).to be_successful + end + end + + describe "GET #show" do + it "retorna resposta de sucesso (200 OK)" do + modelo = create_modelo + get :show, params: { id: modelo.to_param } + expect(response).to be_successful + end + end + + describe "GET #new" do + it "retorna resposta de sucesso (200 OK)" do + get :new + expect(response).to be_successful + end + end + + describe "GET #edit" do + it "retorna resposta de sucesso (200 OK)" do + modelo = create_modelo + get :edit, params: { id: modelo.to_param } + expect(response).to be_successful + end + end + + describe "POST #create" do + context "com parâmetros válidos" do + it "cria um novo Modelo" do + expect { + post :create, params: { modelo: valid_attributes } + }.to change(Modelo, :count).by(1) + end + + it "redireciona para o novo modelo" do + post :create, params: { modelo: valid_attributes } + expect(response).to redirect_to(Modelo.order(created_at: :desc).first) + expect(flash[:notice]).to eq("Modelo criado com sucesso.") + end + end + + context "com parâmetros inválidos" do + it "não cria um novo Modelo" do + expect { + post :create, params: { modelo: invalid_attributes } + }.to change(Modelo, :count).by(0) + end + + it "retorna status 422 (Unprocessable Entity)" do + post :create, params: { modelo: invalid_attributes } + expect(response).to have_http_status(:unprocessable_entity) + end + end + end + + describe "PATCH #update" do + let!(:modelo) { create_modelo } + + context "com parâmetros válidos" do + let(:new_attributes) { + { titulo: "Título Atualizado" } + } + + it "atualiza o Modelo solicitado" do + patch :update, params: { id: modelo.to_param, modelo: new_attributes } + modelo.reload + expect(modelo.titulo).to eq("Título Atualizado") + end + + it "redireciona para o modelo" do + patch :update, params: { id: modelo.to_param, modelo: new_attributes } + expect(response).to redirect_to(modelo) + expect(flash[:notice]).to eq("Modelo atualizado com sucesso.") + end + end + + context "com parâmetros inválidos" do + it "não atualiza o título do modelo" do + old_title = modelo.titulo + patch :update, params: { id: modelo.to_param, modelo: invalid_attributes } + modelo.reload + expect(modelo.titulo).to eq(old_title) + end + + it "retorna status 422 (Unprocessable Entity)" do + patch :update, params: { id: modelo.to_param, modelo: invalid_attributes } + expect(response).to have_http_status(:unprocessable_entity) + end + end + end + + describe "DELETE #destroy" do + let!(:modelo) { create_modelo } + + context "quando o modelo NÃO está em uso" do + before do + allow_any_instance_of(Modelo).to receive(:em_uso?).and_return(false) + end + + it "destrói o modelo solicitado" do + expect { + delete :destroy, params: { id: modelo.to_param } + }.to change(Modelo, :count).by(-1) + end + + it "redireciona para a lista de modelos" do + delete :destroy, params: { id: modelo.to_param } + expect(response).to redirect_to(modelos_url) + expect(flash[:notice]).to eq("Modelo excluído com sucesso.") + end + end + + context "quando o modelo ESTÁ em uso" do + before do + allow_any_instance_of(Modelo).to receive(:em_uso?).and_return(true) + end + + it "NÃO destrói o modelo" do + expect { + delete :destroy, params: { id: modelo.to_param } + }.to change(Modelo, :count).by(0) + end + + it "redireciona para a lista com um alerta" do + delete :destroy, params: { id: modelo.to_param } + expect(response).to redirect_to(modelos_url) + expect(flash[:alert]).to eq("Não é possível excluir um modelo que está em uso.") + end + end + end + + describe "POST #clone" do + let!(:modelo) { create_modelo } + + context "quando a clonagem é bem sucedida" do + it "redireciona para edição do clone" do + novo_modelo = Modelo.new(id: 999, titulo: "Clone") + allow(novo_modelo).to receive(:persisted?).and_return(true) + expect_any_instance_of(Modelo).to receive(:clonar_com_perguntas) + .with("#{modelo.titulo} (Cópia)") + .and_return(novo_modelo) + + post :clone, params: { id: modelo.to_param } + + expect(response).to redirect_to(edit_modelo_path(novo_modelo)) + expect(flash[:notice]).to include("Modelo clonado com sucesso") + end + end + + context "quando a clonagem falha" do + it "redireciona para o modelo original com erro" do + novo_modelo_falho = Modelo.new + allow(novo_modelo_falho).to receive(:persisted?).and_return(false) + + expect_any_instance_of(Modelo).to receive(:clonar_com_perguntas) + .and_return(novo_modelo_falho) + + post :clone, params: { id: modelo.to_param } + + expect(response).to redirect_to(modelo) + expect(flash[:alert]).to eq("Erro ao clonar modelo.") + end + end + end +end