1# frozen_string_literal: true
2
3FactoryBot.define do
4  factory :design_version, class: 'DesignManagement::Version' do
5    sha
6    issue { designs.first&.issue || association(:issue) }
7    author { issue&.author || association(:user) }
8
9    transient do
10      designs_count { 1 }
11      created_designs { [] }
12      modified_designs { [] }
13      deleted_designs { [] }
14    end
15
16    trait :importing do
17      issue { nil }
18
19      designs_count { 0 }
20      importing { true }
21      imported { false }
22    end
23
24    trait :imported do
25      importing { false }
26      imported { true }
27    end
28
29    after(:build) do |version, evaluator|
30      # By default all designs are created_designs, so just add them.
31      specific_designs = [].concat(
32        evaluator.created_designs,
33        evaluator.modified_designs,
34        evaluator.deleted_designs
35      )
36      version.designs += specific_designs
37
38      unless evaluator.designs_count == 0 || version.designs.present?
39        version.designs << create(:design, issue: version.issue)
40      end
41    end
42
43    after :create do |version, evaluator|
44      # FactoryBot does not like methods, so we use lambdas instead
45      events = DesignManagement::Action.events
46
47      version.actions
48        .where(design_id: evaluator.modified_designs.map(&:id))
49        .update_all(event: events[:modification])
50
51      version.actions
52        .where(design_id: evaluator.deleted_designs.map(&:id))
53        .update_all(event: events[:deletion])
54
55      # Ensure version.issue == design.issue for all version.designs
56      version.designs.update_all(issue_id: version.issue_id)
57      version.designs.reload
58
59      needed = evaluator.designs_count
60      have = version.designs.size
61
62      create_list(:design, [0, needed - have].max, issue: version.issue).each do |d|
63        version.designs << d
64      end
65
66      version.actions.reset
67    end
68
69    # Use this trait to build versions with designs that are backed by Git LFS, committed
70    # to the repository, and with an LfsObject correctly created for it.
71    trait :with_lfs_file do
72      committed
73
74      transient do
75        raw_file { fixture_file_upload('spec/fixtures/dk.png', 'image/png') }
76        lfs_pointer { Gitlab::Git::LfsPointerFile.new(SecureRandom.random_bytes) }
77        file { lfs_pointer.pointer }
78      end
79
80      after :create do |version, evaluator|
81        lfs_object = create(:lfs_object, file: evaluator.raw_file, oid: evaluator.lfs_pointer.sha256, size: evaluator.lfs_pointer.size)
82        create(:lfs_objects_project, project: version.project, lfs_object: lfs_object, repository_type: :design)
83      end
84    end
85
86    # This trait is for versions that must be present in the git repository.
87    trait :committed do
88      transient do
89        file { File.join(Rails.root, 'spec/fixtures/dk.png') }
90      end
91
92      after :create do |version, evaluator|
93        project = version.issue.project
94        repository = project.design_repository
95        repository.create_if_not_exists
96
97        designs = version.designs_by_event
98        base_change = { content: evaluator.file }
99
100        actions = %w[modification deletion].flat_map { |k| designs.fetch(k, []) }.map do |design|
101          base_change.merge(action: :create, file_path: design.full_path)
102        end
103
104        if actions.present?
105          repository.multi_action(
106            evaluator.author,
107            branch_name: 'master',
108            message: "created #{actions.size} files",
109            actions: actions
110          )
111        end
112
113        mapping = {
114          'creation' => :create,
115          'modification' => :update,
116          'deletion' => :delete
117        }
118
119        version_actions = designs.flat_map do |(event, designs)|
120          base = event == 'deletion' ? {} : base_change
121          designs.map do |design|
122            base.merge(action: mapping[event], file_path: design.full_path)
123          end
124        end
125
126        sha = repository.multi_action(
127          evaluator.author,
128          branch_name: 'master',
129          message: "edited #{version_actions.size} files",
130          actions: version_actions
131        )
132
133        version.update!(sha: sha)
134      end
135    end
136  end
137end
138