# frozen_string_literal: true

require 'test_helper'

module SerializationScopeTesting
  class User < ActiveModelSerializers::Model
    attributes :id, :name, :admin
    def admin?
      admin
    end
  end
  class Comment < ActiveModelSerializers::Model
    attributes :id, :body
  end
  class Post < ActiveModelSerializers::Model
    attributes :id, :title, :body, :comments
  end
  class PostSerializer < ActiveModel::Serializer
    attributes :id, :title, :body, :comments

    def body
      "The 'scope' is the 'current_user': #{scope == current_user}"
    end

    def comments
      if current_user.admin?
        [Comment.new(id: 1, body: 'Admin')]
      else
        [Comment.new(id: 2, body: 'Scoped')]
      end
    end

    def json_key
      'post'
    end
  end
  class PostTestController < ActionController::Base
    attr_writer :current_user

    def render_post_by_non_admin
      self.current_user = User.new(id: 3, name: 'Pete', admin: false)
      render json: new_post, serializer: serializer, adapter: :json
    end

    def render_post_by_admin
      self.current_user = User.new(id: 3, name: 'Pete', admin: true)
      render json: new_post, serializer: serializer, adapter: :json
    end

    def current_user
      defined?(@current_user) ? @current_user : :current_user_not_set
    end

    private

    def new_post
      Post.new(id: 4, title: 'Title')
    end

    def serializer
      PostSerializer
    end
  end
  class PostViewContextSerializer < PostSerializer
    def body
      "The 'scope' is the 'view_context': #{scope == view_context}"
    end

    def comments
      if view_context.controller.current_user.admin?
        [Comment.new(id: 1, body: 'Admin')]
      else
        [Comment.new(id: 2, body: 'Scoped')]
      end
    end
  end
  class DefaultScopeTest < ActionController::TestCase
    tests PostTestController

    def test_default_serialization_scope
      assert_equal :current_user, @controller._serialization_scope
    end

    def test_default_serialization_scope_object
      assert_equal :current_user_not_set, @controller.current_user
      assert_equal :current_user_not_set, @controller.serialization_scope
    end

    def test_default_scope_non_admin
      get :render_post_by_non_admin
      expected_json = {
        post: {
          id: 4,
          title: 'Title',
          body: "The 'scope' is the 'current_user': true",
          comments: [
            { id: 2, body: 'Scoped' }
          ]
        }
      }.to_json
      assert_equal expected_json, @response.body
    end

    def test_default_scope_admin
      get :render_post_by_admin
      expected_json = {
        post: {
          id: 4,
          title: 'Title',
          body: "The 'scope' is the 'current_user': true",
          comments: [
            { id: 1, body: 'Admin' }
          ]
        }
      }.to_json
      assert_equal expected_json, @response.body
    end
  end
  class SerializationScopeTest < ActionController::TestCase
    class PostViewContextTestController < PostTestController
      serialization_scope :view_context

      private

      def serializer
        PostViewContextSerializer
      end
    end
    tests PostViewContextTestController

    def test_defined_serialization_scope
      assert_equal :view_context, @controller._serialization_scope
    end

    def test_defined_serialization_scope_object
      assert_equal @controller.view_context.controller, @controller.serialization_scope.controller
    end

    def test_serialization_scope_non_admin
      get :render_post_by_non_admin
      expected_json = {
        post: {
          id: 4,
          title: 'Title',
          body: "The 'scope' is the 'view_context': true",
          comments: [
            { id: 2, body: 'Scoped' }
          ]
        }
      }.to_json
      assert_equal expected_json, @response.body
    end

    def test_serialization_scope_admin
      get :render_post_by_admin
      expected_json = {
        post: {
          id: 4,
          title: 'Title',
          body: "The 'scope' is the 'view_context': true",
          comments: [
            { id: 1, body: 'Admin' }
          ]
        }
      }.to_json
      assert_equal expected_json, @response.body
    end
  end
  class NilSerializationScopeTest < ActionController::TestCase
    class PostViewContextTestController < ActionController::Base
      serialization_scope nil

      attr_accessor :current_user

      def render_post_with_no_scope
        self.current_user = User.new(id: 3, name: 'Pete', admin: false)
        render json: new_post, serializer: PostSerializer, adapter: :json
      end

      def render_post_with_passed_in_scope
        self.current_user = User.new(id: 3, name: 'Pete', admin: false)
        render json: new_post, serializer: PostSerializer, adapter: :json, scope: current_user, scope_name: :current_user
      end

      def render_post_with_passed_in_scope_without_scope_name
        self.current_user = User.new(id: 3, name: 'Pete', admin: false)
        render json: new_post, serializer: PostSerializer, adapter: :json, scope: current_user
      end

      private

      def new_post
        Post.new(id: 4, title: 'Title')
      end
    end
    tests PostViewContextTestController

    def test_nil_serialization_scope
      assert_nil @controller._serialization_scope
    end

    def test_nil_serialization_scope_object
      assert_nil @controller.serialization_scope
    end

    def test_nil_scope
      exception_matcher = /current_user/
      exception = assert_raises(NameError) do
        get :render_post_with_no_scope
      end
      assert_match exception_matcher, exception.message
    end

    def test_serialization_scope_is_and_nil_scope_passed_in_current_user
      get :render_post_with_passed_in_scope
      expected_json = {
        post: {
          id: 4,
          title: 'Title',
          body: "The 'scope' is the 'current_user': true",
          comments: [
            { id: 2, body: 'Scoped' }
          ]
        }
      }.to_json
      assert_equal expected_json, @response.body
    end

    def test_serialization_scope_is_nil_and_scope_passed_in_current_user_without_scope_name
      exception_matcher = /current_user/
      exception = assert_raises(NameError) do
        get :render_post_with_passed_in_scope_without_scope_name
      end
      assert_match exception_matcher, exception.message
    end
  end
end
