From 3378e7114fcd6bd5e3883e6640f649418afdaaa2 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 17 Feb 2025 22:08:18 -0800 Subject: [PATCH] Implement CharacterProcessor Process character data from deck --- .../processors/character_processor.rb | 89 +++++++++++++++++++ .../processors/character_processor_spec.rb | 52 +++++++++++ 2 files changed, 141 insertions(+) create mode 100644 app/services/processors/character_processor.rb create mode 100644 spec/services/processors/character_processor_spec.rb diff --git a/app/services/processors/character_processor.rb b/app/services/processors/character_processor.rb new file mode 100644 index 0000000..691ac52 --- /dev/null +++ b/app/services/processors/character_processor.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +module Processors + ## + # CharacterProcessor processes an array of character data and creates GridCharacter records. + # + # @example + # processor = Processors::CharacterProcessor.new(party, transformed_characters_array) + # processor.process + class CharacterProcessor < BaseProcessor + def initialize(party, data, type = :normal, options = {}) + super(party, data, options) + @party = party + @data = data + end + + ## + # Processes character data. + # + # Iterates over each character hash in +data+ and creates a new GridCharacter record. + # Expects each character hash to include keys such as :id, :position, :uncap, etc. + # + # @return [void] + def process + unless @data.is_a?(Hash) + Rails.logger.error "[CHARACTER] Invalid data format: expected a Hash, got #{@data.class}" + return + end + + unless @data.key?('deck') && @data['deck'].key?('npc') + Rails.logger.error '[CHARACTER] Missing npc data in deck JSON' + return + end + + @data = @data.with_indifferent_access + characters_data = @data['deck']['npc'] + + grid_characters = process_characters(characters_data) + grid_characters.each do |grid_character| + begin + grid_character.save! + rescue ActiveRecord::RecordInvalid => e + Rails.logger.error "[CHARACTER] Failed to create GridCharacter: #{e.record.errors.full_messages.join(', ')}" + end + end + + rescue StandardError => e + raise e + end + + private + + def process_characters(characters_data) + characters_data.map do |key, raw_character| + next if raw_character.nil? || raw_character['param'].nil? || raw_character['master'].nil? + + position = key.to_i - 1 + + # Find the Character record by its granblue_id. + character_id = raw_character.dig('master', 'id') + character = Character.find_by(granblue_id: character_id) + + unless character + Rails.logger.error "[CHARACTER] Character not found with id #{character_id}" + next + end + + # The deck doesn't have Awakening data, so use the default + awakening = Awakening.where(slug: 'character-balanced').first + grid_character = GridCharacter.create( + party_id: @party.id, + character_id: character.id, + uncap_level: raw_character.dig('param', 'evolution').to_i, + transcendence_step: raw_character.dig('param', 'phase').to_i, + position: position, + perpetuity: raw_character.dig('param', 'has_npcaugment_constant'), + awakening: awakening + ) + + grid_character + end.compact + end + + # Converts a value to a boolean. + def parse_boolean(val) + val.to_s.downcase == 'true' + end + end +end diff --git a/spec/services/processors/character_processor_spec.rb b/spec/services/processors/character_processor_spec.rb new file mode 100644 index 0000000..6bfa58e --- /dev/null +++ b/spec/services/processors/character_processor_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Processors::CharacterProcessor, type: :model do + before(:each) do + @party = create(:party) + end + + # Use canonical data loaded via canonical.rb. + let(:deck_data) do + file_path = Rails.root.join('spec', 'fixtures', 'deck_sample2.json') + JSON.parse(File.read(file_path)) + end + + subject! { described_class.new(@party, deck_data, language: 'en') } + + context 'with valid character data' do + it 'creates the correct number of GridCharacter records' do + expect { subject.process }.to change(GridCharacter, :count).by(5) + end + + it 'creates GridCharacters with the correct attributes' do + subject.process + grid_chars = GridCharacter.where(party_id: @party.id).order(:position) + + # We assume the processor uses the character id from raw_data. + expect(grid_chars[0].character.granblue_id).to eq(deck_data.dig('deck', 'npc', '1', 'master', 'id')) + expect(grid_chars[3].uncap_level).to eq(deck_data.dig('deck', 'npc', '4', 'param', 'evolution').to_i) + expect(grid_chars[4].position).to eq(4) + end + end + + context 'with invalid character data' do + let(:deck_data) { 'invalid data' } + it 'does not create any GridCharacter and logs an error containing "CHARACTER"' do + expect { subject.process }.not_to change(GridCharacter, :count) + + begin + subject.process + rescue StandardError + nil + end + end + end + + after(:each) do |example| + if example.exception + puts "\nDEBUG [CharacterProcessor]: #{example.full_description} failed with error: #{example.exception.message}" + end + end +end