require "test_helper" class Api::V1::ApiTest < ActionDispatch::IntegrationTest setup do @admin_token = ApiToken.issue!(user: users(:admin), name: "test-admin") @marco_token = ApiToken.issue!(user: users(:marco), name: "test-marco") @org = organizations(:acme) @task = tasks(:follow_up) @opportunity = opportunities(:deal) end test "rejects missing token" do get "/api/v1/projects", as: :json assert_response :unauthorized assert_equal "Non autenticato", json_body["error"] end test "rejects invalid token" do get "/api/v1/projects", headers: bearer("crm_invalid"), as: :json assert_response :unauthorized end test "lists accessible projects" do get "/api/v1/projects", headers: bearer(@marco_token.plaintext), as: :json assert_response :success codes = json_body["projects"].map { |p| p["code"] } assert_includes codes, "matchlivetv" assert_not_includes codes, "riskmeter" end test "forbids project the user cannot access" do get "/api/v1/p/riskmeter/today", headers: bearer(@marco_token.plaintext), as: :json assert_response :forbidden end test "returns 404 for unknown project" do get "/api/v1/p/sconosciuto/today", headers: bearer(@admin_token.plaintext), as: :json assert_response :not_found end test "today includes due tasks" do get "/api/v1/p/matchlivetv/today", headers: bearer(@admin_token.plaintext), as: :json assert_response :success ids = json_body["today_tasks"].map { |t| t["id"] } assert_includes ids, @task.id assert_equal "matchlivetv", json_body["project"]["code"] end test "search finds organization" do get "/api/v1/p/matchlivetv/search", params: { q: "ASD Test" }, headers: bearer(@admin_token.plaintext), as: :json assert_response :success org_ids = json_body["organizations"].map { |o| o["id"] } assert_includes org_ids, @org.id end test "shows organization card" do get "/api/v1/p/matchlivetv/organizations/#{@org.id}", headers: bearer(@admin_token.plaintext), as: :json assert_response :success assert_equal @org.name, json_body["organization"]["name"] assert json_body["contacts"].any? assert json_body["open_opportunities"].any? assert json_body["pending_tasks"].any? end test "creates activity" do assert_difference -> { Activity.count }, 1 do post "/api/v1/p/matchlivetv/activities", params: { organization_id: @org.id, activity_type: "note", subject: "Nota agente", description: "Creato via API" }, headers: bearer(@admin_token.plaintext), as: :json end assert_response :created assert_equal "Nota agente", json_body["activity"]["subject"] end test "creates and completes task" do post "/api/v1/p/matchlivetv/tasks", params: { organization_id: @org.id, title: "Richiamare dopo demo", due_at: 1.day.from_now.iso8601, task_type: "call", priority: "high" }, headers: bearer(@admin_token.plaintext), as: :json assert_response :created task_id = json_body["task"]["id"] post "/api/v1/p/matchlivetv/tasks/#{task_id}/complete", headers: bearer(@admin_token.plaintext), as: :json assert_response :success assert_equal "completed", json_body["task"]["status"] end test "updates opportunity stage" do patch "/api/v1/p/matchlivetv/opportunities/#{@opportunity.id}/stage", params: { pipeline_stage: "demo_trial", notes: "Demo fissata dall'agente" }, headers: bearer(@admin_token.plaintext), as: :json assert_response :success assert_equal "demo_trial", json_body["opportunity"]["pipeline_stage"] @opportunity.reload assert_equal "demo_trial", @opportunity.pipeline_stage end test "updates organization email after bounce" do patch "/api/v1/p/matchlivetv/organizations/#{@org.id}/email", params: { organization_id: @org.id, bounced_email: "vecchia@example.com", email: "nuova@example.com", email_invalid: false, bounce_reason: "User unknown", activity_subject: "Email aggiornata dopo bounce" }, headers: bearer(@admin_token.plaintext), as: :json assert_response :success @org.reload assert_equal "nuova@example.com", @org.email assert_equal "vecchia@example.com", @org.bounced_email assert_not @org.email_invalid? assert json_body["activity"].present? end test "rejects lost stage without reason" do patch "/api/v1/p/matchlivetv/opportunities/#{@opportunity.id}/stage", params: { pipeline_stage: "lost" }, headers: bearer(@admin_token.plaintext), as: :json assert_response :unprocessable_entity end private def bearer(plaintext) { "Authorization" => "Bearer #{plaintext}" } end def json_body JSON.parse(response.body) end end