Add sync schema contract guardrails
This commit is contained in:
parent
f18c37401e
commit
5126e94c26
|
|
@ -1,6 +1,14 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class TournamentSnapshotBuilder
|
class TournamentSnapshotBuilder
|
||||||
|
ROOT_ATTRIBUTES = TournamentSyncSchema.synced_columns_for(Tournament).map(&:to_sym).freeze
|
||||||
|
TEAM_ATTRIBUTES = TournamentSyncSchema.synced_columns_for(Team).map(&:to_sym).freeze
|
||||||
|
STAGE_ATTRIBUTES = TournamentSyncSchema.synced_columns_for(Stage).map(&:to_sym).freeze
|
||||||
|
GROUP_ATTRIBUTES = TournamentSyncSchema.synced_columns_for(Group).map(&:to_sym).freeze
|
||||||
|
MATCH_ATTRIBUTES = TournamentSyncSchema.synced_columns_for(Match).map(&:to_sym).freeze
|
||||||
|
MATCH_SCORE_ATTRIBUTES = TournamentSyncSchema.synced_columns_for(MatchScore).map(&:to_sym).freeze
|
||||||
|
GROUP_SCORE_ATTRIBUTES = TournamentSyncSchema.synced_columns_for(GroupScore).map(&:to_sym).freeze
|
||||||
|
|
||||||
def self.build(tournament)
|
def self.build(tournament)
|
||||||
new(tournament).build
|
new(tournament).build
|
||||||
end
|
end
|
||||||
|
|
@ -10,24 +18,16 @@ class TournamentSnapshotBuilder
|
||||||
end
|
end
|
||||||
|
|
||||||
def build
|
def build
|
||||||
{
|
payload = tournament.attributes.symbolize_keys.slice(*ROOT_ATTRIBUTES)
|
||||||
id: tournament.id,
|
payload[:timer_end] = tournament.timer_end&.iso8601
|
||||||
name: tournament.name,
|
payload[:sync_generated_at] = Time.current.iso8601(6)
|
||||||
code: tournament.code,
|
payload[:teams] = teams_payload
|
||||||
description: tournament.description,
|
payload[:stages] = stages_payload
|
||||||
public: tournament.public,
|
payload[:groups] = groups_payload
|
||||||
playoff_teams_amount: tournament.playoff_teams_amount,
|
payload[:matches] = matches_payload
|
||||||
instant_finalists_amount: tournament.instant_finalists_amount,
|
payload[:match_scores] = match_scores_payload
|
||||||
intermediate_round_participants_amount: tournament.intermediate_round_participants_amount,
|
payload[:group_scores] = group_scores_payload
|
||||||
sync_generated_at: Time.current.iso8601(6),
|
payload
|
||||||
timer_end: tournament.timer_end&.iso8601,
|
|
||||||
teams: teams_payload,
|
|
||||||
stages: stages_payload,
|
|
||||||
groups: groups_payload,
|
|
||||||
matches: matches_payload,
|
|
||||||
match_scores: match_scores_payload,
|
|
||||||
group_scores: group_scores_payload
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
@ -36,69 +36,37 @@ class TournamentSnapshotBuilder
|
||||||
|
|
||||||
def teams_payload
|
def teams_payload
|
||||||
teams_for_snapshot.sort_by(&:id).map do |team|
|
teams_for_snapshot.sort_by(&:id).map do |team|
|
||||||
{
|
team.attributes.symbolize_keys.slice(*TEAM_ATTRIBUTES)
|
||||||
id: team.id,
|
|
||||||
name: team.name
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def stages_payload
|
def stages_payload
|
||||||
tournament.stages.order(:id).map do |stage|
|
tournament.stages.order(:id).map do |stage|
|
||||||
{
|
stage.attributes.symbolize_keys.slice(*STAGE_ATTRIBUTES)
|
||||||
id: stage.id,
|
|
||||||
level: stage.level,
|
|
||||||
state: stage.state
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def groups_payload
|
def groups_payload
|
||||||
tournament.stages.flat_map(&:groups).sort_by(&:id).map do |group|
|
tournament.stages.flat_map(&:groups).sort_by(&:id).map do |group|
|
||||||
{
|
group.attributes.symbolize_keys.slice(*GROUP_ATTRIBUTES)
|
||||||
id: group.id,
|
|
||||||
stage_id: group.stage_id,
|
|
||||||
number: group.number
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def matches_payload
|
def matches_payload
|
||||||
tournament.matches.sort_by(&:id).map do |match|
|
tournament.matches.sort_by(&:id).map do |match|
|
||||||
{
|
match.attributes.symbolize_keys.slice(*MATCH_ATTRIBUTES)
|
||||||
id: match.id,
|
|
||||||
stage_id: match.stage&.id,
|
|
||||||
group_id: match.group&.id,
|
|
||||||
state: match.state,
|
|
||||||
position: match.position,
|
|
||||||
decider: match.decider
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def match_scores_payload
|
def match_scores_payload
|
||||||
tournament.matches.flat_map(&:match_scores).sort_by(&:id).map do |match_score|
|
tournament.matches.flat_map(&:match_scores).sort_by(&:id).map do |match_score|
|
||||||
{
|
match_score.attributes.symbolize_keys.slice(*MATCH_SCORE_ATTRIBUTES)
|
||||||
id: match_score.id,
|
|
||||||
match_id: match_score.match_id,
|
|
||||||
team_id: match_score.team_id,
|
|
||||||
points: match_score.points,
|
|
||||||
hidden_points: match_score.hidden_points
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def group_scores_payload
|
def group_scores_payload
|
||||||
tournament.stages.flat_map(&:groups).flat_map(&:group_scores).sort_by(&:id).map do |group_score|
|
tournament.stages.flat_map(&:groups).flat_map(&:group_scores).sort_by(&:id).map do |group_score|
|
||||||
{
|
group_score.attributes.symbolize_keys.slice(*GROUP_SCORE_ATTRIBUTES)
|
||||||
id: group_score.id,
|
|
||||||
group_id: group_score.group_id,
|
|
||||||
team_id: group_score.team_id,
|
|
||||||
group_points: group_score.group_points,
|
|
||||||
scored_points: group_score.scored_points,
|
|
||||||
received_points: group_score.received_points,
|
|
||||||
position: group_score.position
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TournamentSyncSchema
|
||||||
|
MODEL_COLUMNS = {
|
||||||
|
Tournament => {
|
||||||
|
synced: %w[
|
||||||
|
id
|
||||||
|
name
|
||||||
|
code
|
||||||
|
description
|
||||||
|
public
|
||||||
|
playoff_teams_amount
|
||||||
|
instant_finalists_amount
|
||||||
|
intermediate_round_participants_amount
|
||||||
|
timer_end
|
||||||
|
].freeze,
|
||||||
|
ignored: %w[
|
||||||
|
user_id
|
||||||
|
created_at
|
||||||
|
updated_at
|
||||||
|
read_only_mode
|
||||||
|
sync_target_url
|
||||||
|
sync_auth_token
|
||||||
|
sync_source_tournament_id
|
||||||
|
sync_last_pushed_at
|
||||||
|
sync_last_push_error
|
||||||
|
sync_last_imported_snapshot_at
|
||||||
|
].freeze
|
||||||
|
}.freeze,
|
||||||
|
Team => {
|
||||||
|
synced: %w[id name].freeze,
|
||||||
|
ignored: %w[tournament_id created_at updated_at sync_source_id].freeze
|
||||||
|
}.freeze,
|
||||||
|
Stage => {
|
||||||
|
synced: %w[id level state].freeze,
|
||||||
|
ignored: %w[tournament_id created_at updated_at sync_source_id].freeze
|
||||||
|
}.freeze,
|
||||||
|
Group => {
|
||||||
|
synced: %w[id number stage_id].freeze,
|
||||||
|
ignored: %w[created_at updated_at sync_source_id].freeze
|
||||||
|
}.freeze,
|
||||||
|
Match => {
|
||||||
|
synced: %w[id state position stage_id group_id decider].freeze,
|
||||||
|
ignored: %w[created_at updated_at sync_source_id].freeze
|
||||||
|
}.freeze,
|
||||||
|
MatchScore => {
|
||||||
|
synced: %w[id points match_id team_id hidden_points].freeze,
|
||||||
|
ignored: %w[created_at updated_at sync_source_id].freeze
|
||||||
|
}.freeze,
|
||||||
|
GroupScore => {
|
||||||
|
synced: %w[id group_points scored_points received_points team_id group_id position].freeze,
|
||||||
|
ignored: %w[created_at updated_at sync_source_id].freeze
|
||||||
|
}.freeze
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def synced_columns_for(model_class)
|
||||||
|
MODEL_COLUMNS.fetch(model_class).fetch(:synced)
|
||||||
|
end
|
||||||
|
|
||||||
|
def ignored_columns_for(model_class)
|
||||||
|
MODEL_COLUMNS.fetch(model_class).fetch(:ignored)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe TournamentSyncSchema do
|
||||||
|
describe 'column coverage' do
|
||||||
|
described_class::MODEL_COLUMNS.each_key do |model_class|
|
||||||
|
it "accounts for every #{model_class.name} column in sync contract" do
|
||||||
|
synced_columns = described_class.synced_columns_for(model_class)
|
||||||
|
ignored_columns = described_class.ignored_columns_for(model_class)
|
||||||
|
accounted_columns = synced_columns + ignored_columns
|
||||||
|
|
||||||
|
expect(accounted_columns.uniq).to match_array(model_class.column_names)
|
||||||
|
expect(synced_columns & ignored_columns).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in New Issue