# frozen_string_literal: true

require 'test_helper'

module ActionController
  module Serialization
    class NamespaceLookupTest < ActionController::TestCase
      class Book < ::Model
        attributes :id, :title, :body
        associations :writer, :chapters
      end
      class Chapter < ::Model
        attributes :title
      end
      class Writer < ::Model
        attributes :name
      end

      module Api
        module V2
          class BookSerializer < ActiveModel::Serializer
            attributes :title
          end
        end

        module VHeader
          class BookSerializer < ActiveModel::Serializer
            attributes :title, :body

            def body
              'header'
            end
          end
        end

        module V3
          class BookSerializer < ActiveModel::Serializer
            attributes :title, :body

            belongs_to :writer
            has_many :chapters
          end

          class ChapterSerializer < ActiveModel::Serializer
            attribute :title do
              "Chapter - #{object.title}"
            end
          end

          class WriterSerializer < ActiveModel::Serializer
            attributes :name
          end

          class LookupTestController < ActionController::Base
            before_action only: [:namespace_set_in_before_filter] do
              self.namespace_for_serializer = Api::V2
            end

            def implicit_namespaced_serializer
              writer = Writer.new(name: 'Bob')
              book = Book.new(title: 'New Post', body: 'Body', writer: writer, chapters: [])

              render json: book
            end

            def implicit_namespaced_collection_serializer
              chapter1 = Chapter.new(title: 'Oh')
              chapter2 = Chapter.new(title: 'Oh my')

              render json: [chapter1, chapter2]
            end

            def implicit_has_many_namespaced_serializer
              chapter1 = Chapter.new(title: 'Odd World')
              chapter2 = Chapter.new(title: 'New World')
              book = Book.new(title: 'New Post', body: 'Body', chapters: [chapter1, chapter2])

              render json: book
            end

            def explicit_namespace_as_module
              book = Book.new(title: 'New Post', body: 'Body')

              render json: book, namespace: Api::V2
            end

            def explicit_namespace_as_string
              book = Book.new(title: 'New Post', body: 'Body')

              # because this is a string, ruby can't auto-lookup the constant, so otherwise
              # the lookup thinks we mean ::Api::V2
              render json: book, namespace: 'ActionController::Serialization::NamespaceLookupTest::Api::V2'
            end

            def explicit_namespace_as_symbol
              book = Book.new(title: 'New Post', body: 'Body')

              # because this is a string, ruby can't auto-lookup the constant, so otherwise
              # the lookup thinks we mean ::Api::V2
              render json: book, namespace: :'ActionController::Serialization::NamespaceLookupTest::Api::V2'
            end

            def invalid_namespace
              book = Book.new(id: 'invalid_namespace_book_id', title: 'New Post', body: 'Body')

              render json: book, namespace: :api_v2
            end

            def namespace_set_in_before_filter
              book = Book.new(title: 'New Post', body: 'Body')
              render json: book
            end

            def namespace_set_by_request_headers
              book = Book.new(title: 'New Post', body: 'Body')
              version_from_header = request.headers['X-API_VERSION']
              namespace = "ActionController::Serialization::NamespaceLookupTest::#{version_from_header}"

              render json: book, namespace: namespace
            end
          end
        end
      end

      tests Api::V3::LookupTestController

      setup do
        @test_namespace =
          if Module.method_defined?(:module_parent)
            self.class.module_parent
          else
            self.class.parent
          end
      end

      test 'uses request headers to determine the namespace' do
        request.env['X-API_VERSION'] = 'Api::VHeader'
        get :namespace_set_by_request_headers

        assert_serializer Api::VHeader::BookSerializer
      end

      test 'implicitly uses namespaced serializer' do
        get :implicit_namespaced_serializer

        assert_serializer Api::V3::BookSerializer

        expected = { 'title' => 'New Post', 'body' => 'Body', 'writer' => { 'name' => 'Bob' }, 'chapters' => [] }
        actual = JSON.parse(@response.body)

        assert_equal expected, actual
      end

      test 'implicitly uses namespaced serializer for collection' do
        get :implicit_namespaced_collection_serializer

        assert_serializer 'ActiveModel::Serializer::CollectionSerializer'

        expected = [{ 'title' => 'Chapter - Oh' }, { 'title' => 'Chapter - Oh my' }]
        actual = JSON.parse(@response.body)

        assert_equal expected, actual
      end

      test 'implicitly uses namespaced serializer for has_many' do
        get :implicit_has_many_namespaced_serializer

        assert_serializer Api::V3::BookSerializer

        expected = {
          'title' => 'New Post',
          'body' => 'Body', 'writer' => nil,
          'chapters' => [
            { 'title' => 'Chapter - Odd World' },
            { 'title' => 'Chapter - New World' }
          ]
        }
        actual = JSON.parse(@response.body)

        assert_equal expected, actual
      end

      test 'explicit namespace as module' do
        get :explicit_namespace_as_module

        assert_serializer Api::V2::BookSerializer

        expected = { 'title' => 'New Post' }
        actual = JSON.parse(@response.body)

        assert_equal expected, actual
      end

      test 'explicit namespace as string' do
        get :explicit_namespace_as_string

        assert_serializer Api::V2::BookSerializer

        expected = { 'title' => 'New Post' }
        actual = JSON.parse(@response.body)

        assert_equal expected, actual
      end

      test 'explicit namespace as symbol' do
        get :explicit_namespace_as_symbol

        assert_serializer Api::V2::BookSerializer

        expected = { 'title' => 'New Post' }
        actual = JSON.parse(@response.body)

        assert_equal expected, actual
      end

      test 'invalid namespace' do
        get :invalid_namespace

        assert_serializer ActiveModel::Serializer::Null

        expected = { 'id' => 'invalid_namespace_book_id', 'title' => 'New Post', 'body' => 'Body' }
        actual = JSON.parse(@response.body)

        assert_equal expected, actual
      end

      test 'namespace set in before filter' do
        get :namespace_set_in_before_filter

        assert_serializer Api::V2::BookSerializer

        expected = { 'title' => 'New Post' }
        actual = JSON.parse(@response.body)

        assert_equal expected, actual
      end
    end
  end
end
