add import services for characters, weapons, summons
parses game JSON inventory data and creates collection records 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c498278c89
commit
272f612357
6 changed files with 2279 additions and 0 deletions
169
app/services/character_import_service.rb
Normal file
169
app/services/character_import_service.rb
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
##
|
||||
# Service for importing characters from game JSON data.
|
||||
# Parses the game's character inventory data and creates CollectionCharacter records.
|
||||
#
|
||||
# Note: Unlike weapons and summons, characters are unique per user - each character
|
||||
# can only be in a user's collection once.
|
||||
#
|
||||
# @example Import characters for a user
|
||||
# service = CharacterImportService.new(user, game_data)
|
||||
# result = service.import
|
||||
# if result.success?
|
||||
# puts "Imported #{result.created.size} characters"
|
||||
# end
|
||||
#
|
||||
class CharacterImportService
|
||||
Result = Struct.new(:success?, :created, :updated, :skipped, :errors, keyword_init: true)
|
||||
|
||||
def initialize(user, game_data, options = {})
|
||||
@user = user
|
||||
@game_data = game_data
|
||||
@update_existing = options[:update_existing] || false
|
||||
@created = []
|
||||
@updated = []
|
||||
@skipped = []
|
||||
@errors = []
|
||||
@default_awakening = nil
|
||||
end
|
||||
|
||||
##
|
||||
# Imports characters from game data.
|
||||
#
|
||||
# @return [Result] Import result with counts and errors
|
||||
def import
|
||||
items = extract_items
|
||||
return Result.new(success?: false, created: [], updated: [], skipped: [], errors: ['No character items found in data']) if items.empty?
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
items.each_with_index do |item, index|
|
||||
import_item(item, index)
|
||||
rescue StandardError => e
|
||||
@errors << { index: index, game_id: item.dig('param', 'id'), error: e.message }
|
||||
end
|
||||
end
|
||||
|
||||
Result.new(
|
||||
success?: @errors.empty?,
|
||||
created: @created,
|
||||
updated: @updated,
|
||||
skipped: @skipped,
|
||||
errors: @errors
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def extract_items
|
||||
return @game_data if @game_data.is_a?(Array)
|
||||
return @game_data['list'] if @game_data.is_a?(Hash) && @game_data['list'].is_a?(Array)
|
||||
|
||||
[]
|
||||
end
|
||||
|
||||
def import_item(item, _index)
|
||||
param = item['param'] || {}
|
||||
master = item['master'] || {}
|
||||
|
||||
# The character's granblue_id is in master.id
|
||||
granblue_id = master['id']
|
||||
game_id = param['id']
|
||||
|
||||
character = find_character(granblue_id)
|
||||
unless character
|
||||
@errors << { game_id: game_id, granblue_id: granblue_id, error: 'Character not found' }
|
||||
return
|
||||
end
|
||||
|
||||
# Characters are unique per user - check by character_id, not game_id
|
||||
existing = @user.collection_characters.find_by(character_id: character.id)
|
||||
|
||||
if existing
|
||||
if @update_existing
|
||||
update_existing_character(existing, item, character)
|
||||
else
|
||||
@skipped << { game_id: game_id, character_id: character.id, reason: 'Already exists' }
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
create_collection_character(item, character)
|
||||
end
|
||||
|
||||
def find_character(granblue_id)
|
||||
Character.find_by(granblue_id: granblue_id.to_s)
|
||||
end
|
||||
|
||||
def create_collection_character(item, character)
|
||||
attrs = build_collection_character_attrs(item, character)
|
||||
|
||||
collection_character = @user.collection_characters.build(attrs)
|
||||
|
||||
if collection_character.save
|
||||
@created << collection_character
|
||||
else
|
||||
@errors << {
|
||||
game_id: item.dig('param', 'id'),
|
||||
granblue_id: character.granblue_id,
|
||||
error: collection_character.errors.full_messages.join(', ')
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def update_existing_character(existing, item, character)
|
||||
attrs = build_collection_character_attrs(item, character)
|
||||
|
||||
if existing.update(attrs)
|
||||
@updated << existing
|
||||
else
|
||||
@errors << {
|
||||
game_id: item.dig('param', 'id'),
|
||||
granblue_id: character.granblue_id,
|
||||
error: existing.errors.full_messages.join(', ')
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def build_collection_character_attrs(item, character)
|
||||
param = item['param'] || {}
|
||||
awakening_level = parse_awakening_level(param['arousal_level'])
|
||||
|
||||
attrs = {
|
||||
character: character,
|
||||
uncap_level: parse_uncap_level(param['evolution']),
|
||||
transcendence_step: parse_transcendence_step(param['phase'])
|
||||
}
|
||||
|
||||
# Only set awakening_level if > 1 (requires awakening to be set)
|
||||
# The model's before_save callback will set default awakening
|
||||
if awakening_level > 1
|
||||
attrs[:awakening] = default_awakening
|
||||
attrs[:awakening_level] = awakening_level
|
||||
end
|
||||
|
||||
attrs
|
||||
end
|
||||
|
||||
def default_awakening
|
||||
@default_awakening ||= Awakening.find_by(slug: 'character-balanced', object_type: 'Character')
|
||||
end
|
||||
|
||||
def parse_uncap_level(evolution)
|
||||
value = evolution.to_i
|
||||
# Evolution 6 = transcended, but uncap_level maxes at 5
|
||||
value.clamp(0, 5)
|
||||
end
|
||||
|
||||
def parse_transcendence_step(phase)
|
||||
value = phase.to_i
|
||||
value.clamp(0, 10)
|
||||
end
|
||||
|
||||
def parse_awakening_level(arousal_level)
|
||||
value = arousal_level.to_i
|
||||
# Default to 1 if not present or 0
|
||||
value = 1 if value < 1
|
||||
value.clamp(1, 10)
|
||||
end
|
||||
end
|
||||
146
app/services/summon_import_service.rb
Normal file
146
app/services/summon_import_service.rb
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
##
|
||||
# Service for importing summons from game JSON data.
|
||||
# Parses the game's summon inventory data and creates CollectionSummon records.
|
||||
#
|
||||
# @example Import summons for a user
|
||||
# service = SummonImportService.new(user, game_data)
|
||||
# result = service.import
|
||||
# if result.success?
|
||||
# puts "Imported #{result.created.size} summons"
|
||||
# end
|
||||
#
|
||||
class SummonImportService
|
||||
Result = Struct.new(:success?, :created, :updated, :skipped, :errors, keyword_init: true)
|
||||
|
||||
def initialize(user, game_data, options = {})
|
||||
@user = user
|
||||
@game_data = game_data
|
||||
@update_existing = options[:update_existing] || false
|
||||
@created = []
|
||||
@updated = []
|
||||
@skipped = []
|
||||
@errors = []
|
||||
end
|
||||
|
||||
##
|
||||
# Imports summons from game data.
|
||||
#
|
||||
# @return [Result] Import result with counts and errors
|
||||
def import
|
||||
items = extract_items
|
||||
return Result.new(success?: false, created: [], updated: [], skipped: [], errors: ['No summon items found in data']) if items.empty?
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
items.each_with_index do |item, index|
|
||||
import_item(item, index)
|
||||
rescue StandardError => e
|
||||
@errors << { index: index, game_id: item.dig('param', 'id'), error: e.message }
|
||||
end
|
||||
end
|
||||
|
||||
Result.new(
|
||||
success?: @errors.empty?,
|
||||
created: @created,
|
||||
updated: @updated,
|
||||
skipped: @skipped,
|
||||
errors: @errors
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def extract_items
|
||||
return @game_data if @game_data.is_a?(Array)
|
||||
return @game_data['list'] if @game_data.is_a?(Hash) && @game_data['list'].is_a?(Array)
|
||||
|
||||
[]
|
||||
end
|
||||
|
||||
def import_item(item, _index)
|
||||
param = item['param'] || {}
|
||||
master = item['master'] || {}
|
||||
|
||||
# The summon's granblue_id can be in param.image_id or master.id
|
||||
# image_id may have a suffix like "_04" for transcended summons, so strip it
|
||||
image_id = param['image_id'].to_s.split('_').first if param['image_id'].present?
|
||||
granblue_id = image_id || master['id']
|
||||
game_id = param['id']
|
||||
|
||||
summon = find_summon(granblue_id)
|
||||
unless summon
|
||||
@errors << { game_id: game_id, granblue_id: granblue_id, error: 'Summon not found' }
|
||||
return
|
||||
end
|
||||
|
||||
# Check for existing collection summon with same game ID
|
||||
existing = @user.collection_summons.find_by(game_id: game_id.to_s)
|
||||
|
||||
if existing
|
||||
if @update_existing
|
||||
update_existing_summon(existing, item, summon)
|
||||
else
|
||||
@skipped << { game_id: game_id, reason: 'Already exists' }
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
create_collection_summon(item, summon)
|
||||
end
|
||||
|
||||
def find_summon(granblue_id)
|
||||
Summon.find_by(granblue_id: granblue_id.to_s)
|
||||
end
|
||||
|
||||
def create_collection_summon(item, summon)
|
||||
attrs = build_collection_summon_attrs(item, summon)
|
||||
|
||||
collection_summon = @user.collection_summons.build(attrs)
|
||||
|
||||
if collection_summon.save
|
||||
@created << collection_summon
|
||||
else
|
||||
@errors << {
|
||||
game_id: item.dig('param', 'id'),
|
||||
granblue_id: summon.granblue_id,
|
||||
error: collection_summon.errors.full_messages.join(', ')
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def update_existing_summon(existing, item, summon)
|
||||
attrs = build_collection_summon_attrs(item, summon)
|
||||
|
||||
if existing.update(attrs)
|
||||
@updated << existing
|
||||
else
|
||||
@errors << {
|
||||
game_id: item.dig('param', 'id'),
|
||||
granblue_id: summon.granblue_id,
|
||||
error: existing.errors.full_messages.join(', ')
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def build_collection_summon_attrs(item, summon)
|
||||
param = item['param'] || {}
|
||||
|
||||
{
|
||||
summon: summon,
|
||||
game_id: param['id'].to_s,
|
||||
uncap_level: parse_uncap_level(param['evolution']),
|
||||
transcendence_step: parse_transcendence_step(param['phase'])
|
||||
}
|
||||
end
|
||||
|
||||
def parse_uncap_level(evolution)
|
||||
value = evolution.to_i
|
||||
value.clamp(0, 5)
|
||||
end
|
||||
|
||||
def parse_transcendence_step(phase)
|
||||
value = phase.to_i
|
||||
value.clamp(0, 10)
|
||||
end
|
||||
end
|
||||
266
app/services/weapon_import_service.rb
Normal file
266
app/services/weapon_import_service.rb
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
##
|
||||
# Service for importing weapons from game JSON data.
|
||||
# Parses the game's weapon inventory data and creates CollectionWeapon records.
|
||||
#
|
||||
# @example Import weapons for a user
|
||||
# service = WeaponImportService.new(user, game_data)
|
||||
# result = service.import
|
||||
# if result.success?
|
||||
# puts "Imported #{result.created.size} weapons"
|
||||
# end
|
||||
#
|
||||
class WeaponImportService
|
||||
Result = Struct.new(:success?, :created, :updated, :skipped, :errors, keyword_init: true)
|
||||
|
||||
# Game awakening form to our slug mapping
|
||||
AWAKENING_FORM_MAPPING = {
|
||||
1 => 'weapon-atk', # Attack
|
||||
2 => 'weapon-def', # Defense
|
||||
3 => 'weapon-special', # Special
|
||||
4 => 'weapon-ca', # C.A.
|
||||
5 => 'weapon-skill', # Skill DMG
|
||||
6 => 'weapon-heal' # Healing
|
||||
}.freeze
|
||||
|
||||
def initialize(user, game_data, options = {})
|
||||
@user = user
|
||||
@game_data = game_data
|
||||
@update_existing = options[:update_existing] || false
|
||||
@created = []
|
||||
@updated = []
|
||||
@skipped = []
|
||||
@errors = []
|
||||
@awakening_cache = {}
|
||||
end
|
||||
|
||||
##
|
||||
# Imports weapons from game data.
|
||||
#
|
||||
# @return [Result] Import result with counts and errors
|
||||
def import
|
||||
items = extract_items
|
||||
return Result.new(success?: false, created: [], updated: [], skipped: [], errors: ['No weapon items found in data']) if items.empty?
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
items.each_with_index do |item, index|
|
||||
import_item(item, index)
|
||||
rescue StandardError => e
|
||||
@errors << { index: index, game_id: item.dig('param', 'id'), error: e.message }
|
||||
end
|
||||
end
|
||||
|
||||
Result.new(
|
||||
success?: @errors.empty?,
|
||||
created: @created,
|
||||
updated: @updated,
|
||||
skipped: @skipped,
|
||||
errors: @errors
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def extract_items
|
||||
return @game_data if @game_data.is_a?(Array)
|
||||
return @game_data['list'] if @game_data.is_a?(Hash) && @game_data['list'].is_a?(Array)
|
||||
|
||||
[]
|
||||
end
|
||||
|
||||
def import_item(item, _index)
|
||||
param = item['param'] || {}
|
||||
master = item['master'] || {}
|
||||
|
||||
# The weapon's granblue_id can be in param.image_id or master.id
|
||||
granblue_id = param['image_id'] || master['id']
|
||||
game_id = param['id']
|
||||
|
||||
weapon = find_weapon(granblue_id)
|
||||
unless weapon
|
||||
@errors << { game_id: game_id, granblue_id: granblue_id, error: 'Weapon not found' }
|
||||
return
|
||||
end
|
||||
|
||||
# Check for existing collection weapon with same game ID
|
||||
existing = @user.collection_weapons.find_by(game_id: game_id.to_s)
|
||||
|
||||
if existing
|
||||
if @update_existing
|
||||
update_existing_weapon(existing, item, weapon)
|
||||
else
|
||||
@skipped << { game_id: game_id, reason: 'Already exists' }
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
create_collection_weapon(item, weapon)
|
||||
end
|
||||
|
||||
def find_weapon(granblue_id)
|
||||
Weapon.find_by(granblue_id: granblue_id.to_s)
|
||||
end
|
||||
|
||||
def create_collection_weapon(item, weapon)
|
||||
attrs = build_collection_weapon_attrs(item, weapon)
|
||||
|
||||
collection_weapon = @user.collection_weapons.build(attrs)
|
||||
|
||||
if collection_weapon.save
|
||||
@created << collection_weapon
|
||||
else
|
||||
@errors << {
|
||||
game_id: item.dig('param', 'id'),
|
||||
granblue_id: weapon.granblue_id,
|
||||
error: collection_weapon.errors.full_messages.join(', ')
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def update_existing_weapon(existing, item, weapon)
|
||||
attrs = build_collection_weapon_attrs(item, weapon)
|
||||
|
||||
if existing.update(attrs)
|
||||
@updated << existing
|
||||
else
|
||||
@errors << {
|
||||
game_id: item.dig('param', 'id'),
|
||||
granblue_id: weapon.granblue_id,
|
||||
error: existing.errors.full_messages.join(', ')
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def build_collection_weapon_attrs(item, weapon)
|
||||
param = item['param'] || {}
|
||||
|
||||
attrs = {
|
||||
weapon: weapon,
|
||||
game_id: param['id'].to_s,
|
||||
uncap_level: parse_uncap_level(param['evolution']),
|
||||
transcendence_step: parse_transcendence_step(param['phase'])
|
||||
}
|
||||
|
||||
# Parse awakening if present
|
||||
awakening_attrs = parse_awakening(param['arousal'])
|
||||
attrs.merge!(awakening_attrs) if awakening_attrs
|
||||
|
||||
# Parse AX skills if present
|
||||
ax_attrs = parse_ax_skills(param['augment_skill_info'])
|
||||
attrs.merge!(ax_attrs) if ax_attrs
|
||||
|
||||
attrs
|
||||
end
|
||||
|
||||
def parse_uncap_level(evolution)
|
||||
value = evolution.to_i
|
||||
value.clamp(0, 5)
|
||||
end
|
||||
|
||||
def parse_transcendence_step(phase)
|
||||
value = phase.to_i
|
||||
value.clamp(0, 10)
|
||||
end
|
||||
|
||||
##
|
||||
# Parses awakening data from game format.
|
||||
# Game arousal data contains level and form info.
|
||||
#
|
||||
# @param arousal [Hash] The game's arousal (awakening) data
|
||||
# @return [Hash, nil] Awakening attributes or nil if no awakening
|
||||
def parse_awakening(arousal)
|
||||
return nil if arousal.blank? || arousal['is_arousal_weapon'] != true
|
||||
return nil if arousal['level'].blank?
|
||||
|
||||
form = arousal['form'].to_i
|
||||
awakening = find_awakening_by_form(form)
|
||||
return nil unless awakening
|
||||
|
||||
{
|
||||
awakening_id: awakening.id,
|
||||
awakening_level: arousal['level'].to_i.clamp(1, 20)
|
||||
}
|
||||
end
|
||||
|
||||
def find_awakening_by_form(form)
|
||||
slug = AWAKENING_FORM_MAPPING[form]
|
||||
return nil unless slug
|
||||
|
||||
@awakening_cache[slug] ||= Awakening.find_by(slug: slug, object_type: 'Weapon')
|
||||
end
|
||||
|
||||
##
|
||||
# Parses AX skill data from game format.
|
||||
# Game augment_skill_info is an array of skill arrays.
|
||||
#
|
||||
# @param augment_skill_info [Array] The game's AX skill data
|
||||
# @return [Hash, nil] AX skill attributes or nil if no AX skills
|
||||
def parse_ax_skills(augment_skill_info)
|
||||
return nil if augment_skill_info.blank? || !augment_skill_info.is_a?(Array)
|
||||
|
||||
# First entry in augment_skill_info is an array of skills
|
||||
skills = augment_skill_info.first
|
||||
return nil if skills.blank? || !skills.is_a?(Array)
|
||||
|
||||
attrs = {}
|
||||
|
||||
# First AX skill
|
||||
if skills[0].is_a?(Hash)
|
||||
ax1 = parse_single_ax_skill(skills[0])
|
||||
if ax1
|
||||
attrs[:ax_modifier1] = ax1[:modifier]
|
||||
attrs[:ax_strength1] = ax1[:strength]
|
||||
end
|
||||
end
|
||||
|
||||
# Second AX skill
|
||||
if skills[1].is_a?(Hash)
|
||||
ax2 = parse_single_ax_skill(skills[1])
|
||||
if ax2
|
||||
attrs[:ax_modifier2] = ax2[:modifier]
|
||||
attrs[:ax_strength2] = ax2[:strength]
|
||||
end
|
||||
end
|
||||
|
||||
attrs.empty? ? nil : attrs
|
||||
end
|
||||
|
||||
##
|
||||
# Parses a single AX skill from game data.
|
||||
#
|
||||
# @param skill [Hash] Single AX skill data with skill_id and effect_value
|
||||
# @return [Hash, nil] { modifier:, strength: } or nil
|
||||
def parse_single_ax_skill(skill)
|
||||
return nil unless skill['skill_id'].present?
|
||||
|
||||
# The skill_id maps to our AX modifier
|
||||
modifier = skill['skill_id'].to_i
|
||||
|
||||
# Parse strength from effect_value (may be "3" or "1_3" format)
|
||||
# or from show_value (may be "3%" format)
|
||||
strength = parse_ax_strength(skill['effect_value'], skill['show_value'])
|
||||
|
||||
return nil unless strength
|
||||
|
||||
{ modifier: modifier, strength: strength }
|
||||
end
|
||||
|
||||
def parse_ax_strength(effect_value, show_value)
|
||||
# Try effect_value first
|
||||
if effect_value.present?
|
||||
# Handle "1_3" format (seems to be "tier_value")
|
||||
if effect_value.to_s.include?('_')
|
||||
return effect_value.to_s.split('_').last.to_f
|
||||
end
|
||||
return effect_value.to_f if effect_value.to_s.match?(/\A[\d.]+\z/)
|
||||
end
|
||||
|
||||
# Try show_value (e.g., "3%")
|
||||
if show_value.present?
|
||||
return show_value.to_s.gsub('%', '').to_f
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
end
|
||||
528
spec/services/character_import_service_spec.rb
Normal file
528
spec/services/character_import_service_spec.rb
Normal file
|
|
@ -0,0 +1,528 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe CharacterImportService, type: :service do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
# Create character awakening first (required by model's before_save callback)
|
||||
let!(:awakening_balanced) do
|
||||
Awakening.find_by(slug: 'character-balanced', object_type: 'Character') ||
|
||||
create(:awakening, :for_character, slug: 'character-balanced', name_en: 'Balanced')
|
||||
end
|
||||
|
||||
# Create characters with specific granblue_ids matching the game data
|
||||
# Use unique IDs that won't conflict with seeded data
|
||||
let(:standard_character) do
|
||||
Character.find_by(granblue_id: '3040171000') ||
|
||||
create(:character, granblue_id: '3040171000', name_en: 'Hallessena')
|
||||
end
|
||||
|
||||
let(:flb_character) do
|
||||
Character.find_by(granblue_id: '3040167000') ||
|
||||
create(:character, granblue_id: '3040167000', name_en: 'Zeta')
|
||||
end
|
||||
|
||||
let(:transcendable_character) do
|
||||
Character.find_by(granblue_id: '3040036000') ||
|
||||
create(:character, :transcendable, granblue_id: '3040036000', name_en: 'Siegfried')
|
||||
end
|
||||
|
||||
let(:another_character) do
|
||||
Character.find_by(granblue_id: '3040212000') ||
|
||||
create(:character, granblue_id: '3040212000', name_en: 'Narmaya (Summer)')
|
||||
end
|
||||
|
||||
before do
|
||||
standard_character
|
||||
flb_character
|
||||
transcendable_character
|
||||
another_character
|
||||
end
|
||||
|
||||
describe '#import' do
|
||||
context 'with valid game data' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'master' => {
|
||||
'id' => '3040171000',
|
||||
'max_evolution_level' => 4
|
||||
},
|
||||
'param' => {
|
||||
'id' => 129_355_003,
|
||||
'evolution' => '4',
|
||||
'phase' => '0',
|
||||
'arousal_level' => 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates a collection character' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.size).to eq(1)
|
||||
expect(result.errors).to be_empty
|
||||
end
|
||||
|
||||
it 'sets the correct uncap_level from evolution' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
character = result.created.first
|
||||
expect(character.uncap_level).to eq(4)
|
||||
end
|
||||
|
||||
it 'associates the correct character via granblue_id' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
character = result.created.first
|
||||
expect(character.character.granblue_id).to eq('3040171000')
|
||||
end
|
||||
|
||||
it 'sets awakening_level from arousal_level' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
character = result.created.first
|
||||
expect(character.awakening_level).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with FLB character (evolution 5)' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'master' => {
|
||||
'id' => '3040167000',
|
||||
'max_evolution_level' => 5
|
||||
},
|
||||
'param' => {
|
||||
'id' => 128_935_603,
|
||||
'evolution' => '5',
|
||||
'phase' => '0',
|
||||
'arousal_level' => 9
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'sets uncap_level to 5' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
character = result.created.first
|
||||
expect(character.uncap_level).to eq(5)
|
||||
end
|
||||
|
||||
it 'sets awakening_level from arousal_level' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
character = result.created.first
|
||||
expect(character.awakening_level).to eq(9)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with transcendence data (evolution 6, phase 5)' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'master' => {
|
||||
'id' => '3040036000',
|
||||
'max_evolution_level' => 6
|
||||
},
|
||||
'param' => {
|
||||
'id' => 128_343_789,
|
||||
'evolution' => '6',
|
||||
'phase' => '5',
|
||||
'arousal_level' => 10
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'clamps uncap_level to 5 even when evolution is 6' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
character = result.created.first
|
||||
expect(character.uncap_level).to eq(5)
|
||||
end
|
||||
|
||||
it 'sets transcendence_step from phase' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
character = result.created.first
|
||||
expect(character.transcendence_step).to eq(5)
|
||||
end
|
||||
|
||||
it 'sets max awakening_level' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
character = result.created.first
|
||||
expect(character.awakening_level).to eq(10)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate character (unique per user)' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'master' => {
|
||||
'id' => '3040171000'
|
||||
},
|
||||
'param' => {
|
||||
'id' => 999_999_999,
|
||||
'evolution' => '4',
|
||||
'phase' => '0',
|
||||
'arousal_level' => 5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
create(:collection_character, user: user, character: standard_character)
|
||||
end
|
||||
|
||||
it 'skips the duplicate based on character_id (not game_id)' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.size).to eq(0)
|
||||
expect(result.skipped.size).to eq(1)
|
||||
expect(result.skipped.first[:reason]).to eq('Already exists')
|
||||
end
|
||||
|
||||
context 'with update_existing: true' do
|
||||
let(:game_data_updated) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'master' => {
|
||||
'id' => '3040171000'
|
||||
},
|
||||
'param' => {
|
||||
'id' => 999_999_999,
|
||||
'evolution' => '5',
|
||||
'phase' => '0',
|
||||
'arousal_level' => 10
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates the existing character' do
|
||||
service = described_class.new(user, game_data_updated, update_existing: true)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.size).to eq(0)
|
||||
expect(result.updated.size).to eq(1)
|
||||
expect(result.updated.first.uncap_level).to eq(5)
|
||||
expect(result.updated.first.awakening_level).to eq(10)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with unknown character' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'master' => {
|
||||
'id' => '9999999999'
|
||||
},
|
||||
'param' => {
|
||||
'id' => 12_345,
|
||||
'evolution' => '4',
|
||||
'phase' => '0',
|
||||
'arousal_level' => 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'records an error for the unknown character' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.errors.size).to eq(1)
|
||||
expect(result.errors.first[:error]).to eq('Character not found')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with multiple characters' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'master' => { 'id' => '3040171000' },
|
||||
'param' => {
|
||||
'id' => 111_111_111,
|
||||
'evolution' => '4',
|
||||
'phase' => '0',
|
||||
'arousal_level' => 1
|
||||
}
|
||||
},
|
||||
{
|
||||
'master' => { 'id' => '3040212000' },
|
||||
'param' => {
|
||||
'id' => 222_222_222,
|
||||
'evolution' => '5',
|
||||
'phase' => '0',
|
||||
'arousal_level' => 7
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'imports all characters' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.size).to eq(2)
|
||||
end
|
||||
|
||||
it 'associates correct characters' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
granblue_ids = result.created.map { |c| c.character.granblue_id }.sort
|
||||
expect(granblue_ids).to eq(%w[3040171000 3040212000])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with empty data' do
|
||||
let(:game_data) { { 'list' => [] } }
|
||||
|
||||
it 'returns an error' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be false
|
||||
expect(result.errors).to include('No character items found in data')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with array format data' do
|
||||
let(:game_data) do
|
||||
[
|
||||
{
|
||||
'master' => { 'id' => '3040171000' },
|
||||
'param' => {
|
||||
'id' => 777_777_777,
|
||||
'evolution' => '4',
|
||||
'phase' => '0',
|
||||
'arousal_level' => 5
|
||||
}
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
it 'handles array format correctly' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.size).to eq(1)
|
||||
expect(result.created.first.awakening_level).to eq(5)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with max values' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'master' => { 'id' => '3040036000' },
|
||||
'param' => {
|
||||
'id' => 888_888_888,
|
||||
'evolution' => '10',
|
||||
'phase' => '15',
|
||||
'arousal_level' => 99
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'clamps uncap_level to max 5' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
character = result.created.first
|
||||
expect(character.uncap_level).to eq(5)
|
||||
end
|
||||
|
||||
it 'clamps transcendence_step to max 10' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
character = result.created.first
|
||||
expect(character.transcendence_step).to eq(10)
|
||||
end
|
||||
|
||||
it 'clamps awakening_level to max 10' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
character = result.created.first
|
||||
expect(character.awakening_level).to eq(10)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'edge cases' do
|
||||
context 'with nil arousal_level' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'master' => { 'id' => '3040171000' },
|
||||
'param' => {
|
||||
'id' => 555_555_555,
|
||||
'evolution' => '4',
|
||||
'phase' => '0',
|
||||
'arousal_level' => nil
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'defaults awakening_level to 1' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.first.awakening_level).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with arousal_level 0' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'master' => { 'id' => '3040171000' },
|
||||
'param' => {
|
||||
'id' => 444_444_444,
|
||||
'evolution' => '4',
|
||||
'phase' => '0',
|
||||
'arousal_level' => 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'defaults awakening_level to 1' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.first.awakening_level).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with string evolution values' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'master' => { 'id' => '3040171000' },
|
||||
'param' => {
|
||||
'id' => 333_333_333,
|
||||
'evolution' => '5',
|
||||
'phase' => '0',
|
||||
'arousal_level' => '7'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'handles string values correctly' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.first.uncap_level).to eq(5)
|
||||
expect(result.created.first.awakening_level).to eq(7)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with missing param fields' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'master' => { 'id' => '3040171000' },
|
||||
'param' => {
|
||||
'id' => 222_222_222
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'handles missing fields gracefully (defaults to 0/1)' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
character = result.created.first
|
||||
expect(character.uncap_level).to eq(0)
|
||||
expect(character.transcendence_step).to eq(0)
|
||||
expect(character.awakening_level).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'assigns default awakening via model callback' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'master' => { 'id' => '3040171000' },
|
||||
'param' => {
|
||||
'id' => 111_111_111,
|
||||
'evolution' => '4',
|
||||
'phase' => '0',
|
||||
'arousal_level' => 5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'has default awakening set by model' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.first.awakening).to eq(awakening_balanced)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
499
spec/services/summon_import_service_spec.rb
Normal file
499
spec/services/summon_import_service_spec.rb
Normal file
|
|
@ -0,0 +1,499 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe SummonImportService, type: :service do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
# Create summons with specific granblue_ids matching the game data
|
||||
let(:standard_summon) do
|
||||
Summon.find_by(granblue_id: '2040035000') ||
|
||||
create(:summon, granblue_id: '2040035000', name_en: 'Celeste')
|
||||
end
|
||||
|
||||
let(:flb_summon) do
|
||||
Summon.find_by(granblue_id: '2040445000') ||
|
||||
create(:summon, granblue_id: '2040445000', name_en: 'Typhon')
|
||||
end
|
||||
|
||||
let(:ulb_summon) do
|
||||
Summon.find_by(granblue_id: '2040379000') ||
|
||||
create(:summon, granblue_id: '2040379000', name_en: 'Gorilla')
|
||||
end
|
||||
|
||||
let(:transcendable_summon) do
|
||||
Summon.find_by(granblue_id: '2040100000') ||
|
||||
create(:summon, :transcendable, granblue_id: '2040100000', name_en: 'Bahamut')
|
||||
end
|
||||
|
||||
before do
|
||||
standard_summon
|
||||
flb_summon
|
||||
ulb_summon
|
||||
transcendable_summon
|
||||
end
|
||||
|
||||
describe '#import' do
|
||||
context 'with valid game data' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => 1_500_667_184,
|
||||
'image_id' => '2040035000',
|
||||
'level' => '1',
|
||||
'evolution' => '0',
|
||||
'phase' => '0'
|
||||
},
|
||||
'master' => {
|
||||
'id' => 2_040_035_000,
|
||||
'rarity' => '4'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates a collection summon' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.size).to eq(1)
|
||||
expect(result.errors).to be_empty
|
||||
end
|
||||
|
||||
it 'sets the correct game_id' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
summon = result.created.first
|
||||
expect(summon.game_id).to eq('1500667184')
|
||||
end
|
||||
|
||||
it 'sets the correct uncap_level from evolution' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
summon = result.created.first
|
||||
expect(summon.uncap_level).to eq(0)
|
||||
end
|
||||
|
||||
it 'associates the correct summon via granblue_id' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
summon = result.created.first
|
||||
expect(summon.summon.granblue_id).to eq('2040035000')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with FLB summon (evolution 4)' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => 1_499_006_961,
|
||||
'image_id' => '2040445000',
|
||||
'level' => '150',
|
||||
'evolution' => '4',
|
||||
'phase' => '0'
|
||||
},
|
||||
'master' => {
|
||||
'id' => 2_040_445_000,
|
||||
'rarity' => '4'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'sets uncap_level to 4' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
summon = result.created.first
|
||||
expect(summon.uncap_level).to eq(4)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with ULB summon (evolution 5)' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => 1_494_986_603,
|
||||
'image_id' => '2040379000',
|
||||
'level' => '200',
|
||||
'evolution' => '5',
|
||||
'phase' => '0'
|
||||
},
|
||||
'master' => {
|
||||
'id' => 2_040_379_000,
|
||||
'rarity' => '4'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'sets uncap_level to 5' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
summon = result.created.first
|
||||
expect(summon.uncap_level).to eq(5)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with transcendence data (evolution 6, phase 5)' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => 1_088_016_859,
|
||||
'image_id' => '2040100000_04',
|
||||
'level' => '250',
|
||||
'evolution' => '6',
|
||||
'phase' => '5'
|
||||
},
|
||||
'master' => {
|
||||
'id' => 2_040_100_000,
|
||||
'rarity' => '4'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'clamps uncap_level to 5 even when evolution is 6' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
summon = result.created.first
|
||||
expect(summon.uncap_level).to eq(5)
|
||||
end
|
||||
|
||||
it 'sets transcendence_step from phase' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
summon = result.created.first
|
||||
expect(summon.transcendence_step).to eq(5)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate game_id' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => 9_999_9999,
|
||||
'image_id' => '2040035000',
|
||||
'evolution' => '3',
|
||||
'phase' => '0'
|
||||
},
|
||||
'master' => { 'id' => 2_040_035_000 }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
create(:collection_summon, user: user, summon: standard_summon, game_id: '99999999')
|
||||
end
|
||||
|
||||
it 'skips the duplicate' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.size).to eq(0)
|
||||
expect(result.skipped.size).to eq(1)
|
||||
expect(result.skipped.first[:reason]).to eq('Already exists')
|
||||
end
|
||||
|
||||
context 'with update_existing: true' do
|
||||
let(:game_data_updated) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => 9_999_9999,
|
||||
'image_id' => '2040035000',
|
||||
'evolution' => '5',
|
||||
'phase' => '0'
|
||||
},
|
||||
'master' => { 'id' => 2_040_035_000 }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates the existing summon' do
|
||||
service = described_class.new(user, game_data_updated, update_existing: true)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.size).to eq(0)
|
||||
expect(result.updated.size).to eq(1)
|
||||
expect(result.updated.first.uncap_level).to eq(5)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with unknown summon' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => 12_345,
|
||||
'image_id' => '9999999999',
|
||||
'evolution' => '3',
|
||||
'phase' => '0'
|
||||
},
|
||||
'master' => { 'id' => 9_999_999_999 }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'records an error for the unknown summon' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.errors.size).to eq(1)
|
||||
expect(result.errors.first[:error]).to eq('Summon not found')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with multiple summons' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => 1_111_1111,
|
||||
'image_id' => '2040035000',
|
||||
'evolution' => '0',
|
||||
'phase' => '0'
|
||||
},
|
||||
'master' => { 'id' => 2_040_035_000 }
|
||||
},
|
||||
{
|
||||
'param' => {
|
||||
'id' => 2_222_2222,
|
||||
'image_id' => '2040445000',
|
||||
'evolution' => '4',
|
||||
'phase' => '0'
|
||||
},
|
||||
'master' => { 'id' => 2_040_445_000 }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'imports all summons' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.size).to eq(2)
|
||||
end
|
||||
|
||||
it 'associates correct summons' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
summons = result.created.sort_by(&:game_id)
|
||||
expect(summons[0].summon.granblue_id).to eq('2040035000')
|
||||
expect(summons[1].summon.granblue_id).to eq('2040445000')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with empty data' do
|
||||
let(:game_data) { { 'list' => [] } }
|
||||
|
||||
it 'returns an error' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be false
|
||||
expect(result.errors).to include('No summon items found in data')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with array format data' do
|
||||
let(:game_data) do
|
||||
[
|
||||
{
|
||||
'param' => {
|
||||
'id' => 7_777_7777,
|
||||
'image_id' => '2040035000',
|
||||
'evolution' => '3',
|
||||
'phase' => '0'
|
||||
},
|
||||
'master' => { 'id' => 2_040_035_000 }
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
it 'handles array format correctly' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.size).to eq(1)
|
||||
expect(result.created.first.uncap_level).to eq(3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with image_id containing suffix' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => 8_888_8888,
|
||||
'image_id' => '2040100000_04',
|
||||
'evolution' => '6',
|
||||
'phase' => '5'
|
||||
},
|
||||
'master' => { 'id' => 2_040_100_000 }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'uses master.id when image_id has suffix' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
# Uses master.id since image_id with _04 suffix won't match
|
||||
expect(result.created.first.summon.granblue_id).to eq('2040100000')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with max values' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => 6_666_6666,
|
||||
'image_id' => '2040100000',
|
||||
'evolution' => '10',
|
||||
'phase' => '15'
|
||||
},
|
||||
'master' => { 'id' => 2_040_100_000 }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'clamps uncap_level to max 5' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
summon = result.created.first
|
||||
expect(summon.uncap_level).to eq(5)
|
||||
end
|
||||
|
||||
it 'clamps transcendence_step to max 10' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
summon = result.created.first
|
||||
expect(summon.transcendence_step).to eq(10)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'edge cases' do
|
||||
context 'with nil param values' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => 5_555_5555,
|
||||
'image_id' => '2040035000',
|
||||
'evolution' => nil,
|
||||
'phase' => nil
|
||||
},
|
||||
'master' => { 'id' => 2_040_035_000 }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'handles nil values gracefully (defaults to 0)' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.first.uncap_level).to eq(0)
|
||||
expect(result.created.first.transcendence_step).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with string evolution values' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => 4_444_4444,
|
||||
'image_id' => '2040035000',
|
||||
'evolution' => '4',
|
||||
'phase' => '0'
|
||||
},
|
||||
'master' => { 'id' => 2_040_035_000 }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'handles string evolution values' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.first.uncap_level).to eq(4)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with master.id fallback' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => 3_333_3333,
|
||||
'evolution' => '3',
|
||||
'phase' => '0'
|
||||
},
|
||||
'master' => {
|
||||
'id' => 2_040_035_000
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'uses master.id when param.image_id is missing' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.first.summon.granblue_id).to eq('2040035000')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
671
spec/services/weapon_import_service_spec.rb
Normal file
671
spec/services/weapon_import_service_spec.rb
Normal file
|
|
@ -0,0 +1,671 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe WeaponImportService, type: :service do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
# Create weapons with specific granblue_ids matching the game data
|
||||
let(:standard_weapon) do
|
||||
Weapon.find_by(granblue_id: '1040020000') ||
|
||||
create(:weapon, granblue_id: '1040020000', name_en: 'Luminiera Sword Omega')
|
||||
end
|
||||
|
||||
let(:transcendable_weapon) do
|
||||
Weapon.find_by(granblue_id: '1040310600') ||
|
||||
create(:weapon, :transcendable, granblue_id: '1040310600', name_en: 'Yggdrasil Crystal Blade Omega')
|
||||
end
|
||||
|
||||
let(:awakened_weapon) do
|
||||
Weapon.find_by(granblue_id: '1040914400') ||
|
||||
create(:weapon, granblue_id: '1040914400', name_en: 'Yamato Katana')
|
||||
end
|
||||
|
||||
let(:ax_weapon) do
|
||||
Weapon.find_by(granblue_id: '1040213900') ||
|
||||
create(:weapon, :ax_weapon, granblue_id: '1040213900', name_en: 'Celeste Claw Omega')
|
||||
end
|
||||
|
||||
# Create weapon awakenings
|
||||
let!(:awakening_atk) do
|
||||
Awakening.find_by(slug: 'weapon-atk', object_type: 'Weapon') ||
|
||||
create(:awakening, :for_weapon, slug: 'weapon-atk', name_en: 'Attack')
|
||||
end
|
||||
|
||||
let!(:awakening_def) do
|
||||
Awakening.find_by(slug: 'weapon-def', object_type: 'Weapon') ||
|
||||
create(:awakening, :for_weapon, slug: 'weapon-def', name_en: 'Defense')
|
||||
end
|
||||
|
||||
let!(:awakening_special) do
|
||||
Awakening.find_by(slug: 'weapon-special', object_type: 'Weapon') ||
|
||||
create(:awakening, :for_weapon, slug: 'weapon-special', name_en: 'Special')
|
||||
end
|
||||
|
||||
let!(:awakening_ca) do
|
||||
Awakening.find_by(slug: 'weapon-ca', object_type: 'Weapon') ||
|
||||
create(:awakening, :for_weapon, slug: 'weapon-ca', name_en: 'C.A.')
|
||||
end
|
||||
|
||||
let!(:awakening_skill) do
|
||||
Awakening.find_by(slug: 'weapon-skill', object_type: 'Weapon') ||
|
||||
create(:awakening, :for_weapon, slug: 'weapon-skill', name_en: 'Skill DMG')
|
||||
end
|
||||
|
||||
let!(:awakening_heal) do
|
||||
Awakening.find_by(slug: 'weapon-heal', object_type: 'Weapon') ||
|
||||
create(:awakening, :for_weapon, slug: 'weapon-heal', name_en: 'Healing')
|
||||
end
|
||||
|
||||
before do
|
||||
standard_weapon
|
||||
transcendable_weapon
|
||||
awakened_weapon
|
||||
ax_weapon
|
||||
end
|
||||
|
||||
describe '#import' do
|
||||
context 'with valid game data' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => '49858531',
|
||||
'image_id' => '1040020000',
|
||||
'evolution' => 3,
|
||||
'phase' => 0
|
||||
},
|
||||
'master' => {
|
||||
'id' => '1040020000',
|
||||
'name' => 'Luminiera Sword Omega'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates a collection weapon' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.size).to eq(1)
|
||||
expect(result.errors).to be_empty
|
||||
end
|
||||
|
||||
it 'sets the correct game_id' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
weapon = result.created.first
|
||||
expect(weapon.game_id).to eq('49858531')
|
||||
end
|
||||
|
||||
it 'sets the correct uncap_level from evolution' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
weapon = result.created.first
|
||||
expect(weapon.uncap_level).to eq(3)
|
||||
end
|
||||
|
||||
it 'associates the correct weapon via granblue_id' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
weapon = result.created.first
|
||||
expect(weapon.weapon.granblue_id).to eq('1040020000')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with transcendence data' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => '53169135',
|
||||
'image_id' => '1040310600',
|
||||
'evolution' => 5,
|
||||
'phase' => 6
|
||||
},
|
||||
'master' => {
|
||||
'id' => '1040310600',
|
||||
'name' => 'Yggdrasil Crystal Blade Omega'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'sets transcendence_step from phase' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
weapon = result.created.first
|
||||
expect(weapon.transcendence_step).to eq(6)
|
||||
end
|
||||
|
||||
it 'sets uncap_level to 5 for transcended weapons' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
weapon = result.created.first
|
||||
expect(weapon.uncap_level).to eq(5)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with awakening data' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => '96548732',
|
||||
'image_id' => '1040914400',
|
||||
'evolution' => 5,
|
||||
'phase' => 0,
|
||||
'arousal' => {
|
||||
'is_arousal_weapon' => true,
|
||||
'level' => 15,
|
||||
'form' => 1,
|
||||
'form_name' => 'Attack'
|
||||
}
|
||||
},
|
||||
'master' => {
|
||||
'id' => '1040914400',
|
||||
'name' => 'Yamato Katana'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'sets awakening_id based on form' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
weapon = result.created.first
|
||||
expect(weapon.awakening).to eq(awakening_atk)
|
||||
end
|
||||
|
||||
it 'sets awakening_level from arousal level' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
weapon = result.created.first
|
||||
expect(weapon.awakening_level).to eq(15)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with different awakening forms' do
|
||||
{
|
||||
1 => 'weapon-atk',
|
||||
2 => 'weapon-def',
|
||||
3 => 'weapon-special',
|
||||
4 => 'weapon-ca',
|
||||
5 => 'weapon-skill',
|
||||
6 => 'weapon-heal'
|
||||
}.each do |form, slug|
|
||||
it "maps awakening form #{form} to #{slug}" do
|
||||
game_data = {
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => "test_#{form}",
|
||||
'image_id' => '1040914400',
|
||||
'evolution' => 5,
|
||||
'phase' => 0,
|
||||
'arousal' => {
|
||||
'is_arousal_weapon' => true,
|
||||
'level' => 10,
|
||||
'form' => form
|
||||
}
|
||||
},
|
||||
'master' => { 'id' => '1040914400' }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.created.first.awakening.slug).to eq(slug)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with no awakening' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => '12345678',
|
||||
'image_id' => '1040020000',
|
||||
'evolution' => 3,
|
||||
'phase' => 0,
|
||||
'arousal' => {
|
||||
'is_arousal_weapon' => false
|
||||
}
|
||||
},
|
||||
'master' => { 'id' => '1040020000' }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'does not set awakening when is_arousal_weapon is false' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
weapon = result.created.first
|
||||
expect(weapon.awakening).to be_nil
|
||||
expect(weapon.awakening_level).to eq(1) # default value
|
||||
end
|
||||
end
|
||||
|
||||
context 'with AX skills' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => '55555555',
|
||||
'image_id' => '1040213900',
|
||||
'evolution' => 5,
|
||||
'phase' => 0,
|
||||
'augment_skill_info' => [
|
||||
[
|
||||
{
|
||||
'skill_id' => 1,
|
||||
'effect_value' => '7',
|
||||
'show_value' => '7%'
|
||||
},
|
||||
{
|
||||
'skill_id' => 2,
|
||||
'effect_value' => '2_4',
|
||||
'show_value' => '4%'
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
'master' => { 'id' => '1040213900' }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'parses first AX skill correctly' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
weapon = result.created.first
|
||||
expect(weapon.ax_modifier1).to eq(1)
|
||||
expect(weapon.ax_strength1).to eq(7.0)
|
||||
end
|
||||
|
||||
it 'parses second AX skill with underscore format' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
weapon = result.created.first
|
||||
expect(weapon.ax_modifier2).to eq(2)
|
||||
expect(weapon.ax_strength2).to eq(4.0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with show_value format for AX strength' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => '66666666',
|
||||
'image_id' => '1040213900',
|
||||
'evolution' => 5,
|
||||
'phase' => 0,
|
||||
'augment_skill_info' => [
|
||||
[
|
||||
{
|
||||
'skill_id' => 3,
|
||||
'effect_value' => nil,
|
||||
'show_value' => '5.5%'
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
'master' => { 'id' => '1040213900' }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'parses strength from show_value when effect_value is nil' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
weapon = result.created.first
|
||||
expect(weapon.ax_modifier1).to eq(3)
|
||||
expect(weapon.ax_strength1).to eq(5.5)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate game_id' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => '99999999',
|
||||
'image_id' => '1040020000',
|
||||
'evolution' => 3,
|
||||
'phase' => 0
|
||||
},
|
||||
'master' => { 'id' => '1040020000' }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
create(:collection_weapon, user: user, weapon: standard_weapon, game_id: '99999999')
|
||||
end
|
||||
|
||||
it 'skips the duplicate' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.size).to eq(0)
|
||||
expect(result.skipped.size).to eq(1)
|
||||
expect(result.skipped.first[:reason]).to eq('Already exists')
|
||||
end
|
||||
|
||||
context 'with update_existing: true' do
|
||||
let(:game_data_updated) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => '99999999',
|
||||
'image_id' => '1040020000',
|
||||
'evolution' => 5,
|
||||
'phase' => 0
|
||||
},
|
||||
'master' => { 'id' => '1040020000' }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates the existing weapon' do
|
||||
service = described_class.new(user, game_data_updated, update_existing: true)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.size).to eq(0)
|
||||
expect(result.updated.size).to eq(1)
|
||||
expect(result.updated.first.uncap_level).to eq(5)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with unknown weapon' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => '12345',
|
||||
'image_id' => '9999999999',
|
||||
'evolution' => 3,
|
||||
'phase' => 0
|
||||
},
|
||||
'master' => { 'id' => '9999999999' }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'records an error for the unknown weapon' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.errors.size).to eq(1)
|
||||
expect(result.errors.first[:error]).to eq('Weapon not found')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with multiple weapons' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => '11111111',
|
||||
'image_id' => '1040020000',
|
||||
'evolution' => 3,
|
||||
'phase' => 0
|
||||
},
|
||||
'master' => { 'id' => '1040020000' }
|
||||
},
|
||||
{
|
||||
'param' => {
|
||||
'id' => '22222222',
|
||||
'image_id' => '1040310600',
|
||||
'evolution' => 5,
|
||||
'phase' => 3
|
||||
},
|
||||
'master' => { 'id' => '1040310600' }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'imports all weapons' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.size).to eq(2)
|
||||
end
|
||||
|
||||
it 'associates correct weapons' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
weapons = result.created.sort_by(&:game_id)
|
||||
expect(weapons[0].weapon.granblue_id).to eq('1040020000')
|
||||
expect(weapons[1].weapon.granblue_id).to eq('1040310600')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with empty data' do
|
||||
let(:game_data) { { 'list' => [] } }
|
||||
|
||||
it 'returns an error' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be false
|
||||
expect(result.errors).to include('No weapon items found in data')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with array format data' do
|
||||
let(:game_data) do
|
||||
[
|
||||
{
|
||||
'param' => {
|
||||
'id' => '77777777',
|
||||
'image_id' => '1040020000',
|
||||
'evolution' => 4,
|
||||
'phase' => 0
|
||||
},
|
||||
'master' => { 'id' => '1040020000' }
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
it 'handles array format correctly' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.size).to eq(1)
|
||||
expect(result.created.first.uncap_level).to eq(4)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with max uncap/transcendence values' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => '88888888',
|
||||
'image_id' => '1040310600',
|
||||
'evolution' => 10,
|
||||
'phase' => 15
|
||||
},
|
||||
'master' => { 'id' => '1040310600' }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'clamps uncap_level to max 5' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
weapon = result.created.first
|
||||
expect(weapon.uncap_level).to eq(5)
|
||||
end
|
||||
|
||||
it 'clamps transcendence_step to max 10' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
weapon = result.created.first
|
||||
expect(weapon.transcendence_step).to eq(10)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with max awakening level' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => '10101010',
|
||||
'image_id' => '1040914400',
|
||||
'evolution' => 5,
|
||||
'phase' => 0,
|
||||
'arousal' => {
|
||||
'is_arousal_weapon' => true,
|
||||
'level' => 25,
|
||||
'form' => 1
|
||||
}
|
||||
},
|
||||
'master' => { 'id' => '1040914400' }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'clamps awakening_level to max 20' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
weapon = result.created.first
|
||||
expect(weapon.awakening_level).to eq(20)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with image_id from master.id fallback' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => '33333333',
|
||||
'evolution' => 3,
|
||||
'phase' => 0
|
||||
},
|
||||
'master' => {
|
||||
'id' => '1040020000'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'uses master.id when param.image_id is missing' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.first.weapon.granblue_id).to eq('1040020000')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'edge cases' do
|
||||
context 'with nil arousal' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => '44444444',
|
||||
'image_id' => '1040020000',
|
||||
'evolution' => 3,
|
||||
'phase' => 0,
|
||||
'arousal' => nil
|
||||
},
|
||||
'master' => { 'id' => '1040020000' }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'handles nil arousal gracefully' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.first.awakening).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with empty augment_skill_info' do
|
||||
let(:game_data) do
|
||||
{
|
||||
'list' => [
|
||||
{
|
||||
'param' => {
|
||||
'id' => '55556666',
|
||||
'image_id' => '1040213900',
|
||||
'evolution' => 5,
|
||||
'phase' => 0,
|
||||
'augment_skill_info' => []
|
||||
},
|
||||
'master' => { 'id' => '1040213900' }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'handles empty augment_skill_info gracefully' do
|
||||
service = described_class.new(user, game_data)
|
||||
result = service.import
|
||||
|
||||
expect(result.success?).to be true
|
||||
expect(result.created.first.ax_modifier1).to be_nil
|
||||
expect(result.created.first.ax_modifier2).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in a new issue