• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pulibrary / tigerdata-app / dca7f983-7828-4892-9134-280c636425aa

10 Nov 2025 08:29PM UTC coverage: 91.275% (-0.1%) from 91.371%
dca7f983-7828-4892-9134-280c636425aa

Pull #2177

circleci

hectorcorrea
Fixes access error bug
Pull Request #2177: Handles access error gracefully

1 of 2 new or added lines in 1 file covered. (50.0%)

1757 existing lines in 75 files now uncovered.

2856 of 3129 relevant lines covered (91.28%)

547.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.48
/app/models/mediaflux/asset_metadata_request.rb
1
# frozen_string_literal: true
2
module Mediaflux
5✔
3
  # Get metadata about an asset in mediaflux
4
  # @example
5
  #   metadata_request = Mediaflux::AssetMetadataRequest.new(
6
  #   session_token: current_user.mediaflux_session, id: mediaflux_id).metadata
7
  class AssetMetadataRequest < Request
5✔
8
    attr_reader :id
5✔
9

10
    # Constructor
11
    # @param session_token [String] the API token for the authenticated session
12
    # @param id [Integer] Id of the Asset to return the metadata for
13
    def initialize(session_token:, id:)
5✔
14
      super(session_token: session_token)
466✔
15
      @id = id
466✔
16
    end
17

18
    # Specifies the Mediaflux service to use when getting asset metadata
19
    # @return [String]
20
    def self.service
5✔
21
      "asset.get"
932✔
22
    end
23

24
    # parse the returned XML into a hash about the asset that can be utilized
25
    def metadata
5✔
26
      xml = response_xml
462✔
27
      asset = xml.xpath("/response/reply/result/asset")
458✔
28
      metadata = parse(asset)
458✔
29

30
      if metadata[:collection]
458✔
31
        metadata[:total_file_count] = asset.xpath("./collection/accumulator/value/non-collections").text
454✔
32
        metadata[:size] = asset.xpath("./collection/accumulator/value/total/@h").text
454✔
33
        metadata[:accum_names] = asset.xpath("./collection/accumulator/@name")
454✔
34
        metadata[:ctime] = asset.xpath("./ctime")
454✔
35
      end
36

37
      parse_image(asset.xpath("./meta/mf-image"), metadata) # this does not do anything because mf-image is not a part of the meta xpath
458✔
38

39
      parse_note(asset.xpath("./meta/mf-note"), metadata) # this does not do anything because mf-note is not a part of the meta xpath
458✔
40

41
      parse_quota(asset.xpath("./collection/quota"), metadata)
458✔
42
      metadata
458✔
43
    end
44

45
    private
5✔
46

47
      def build_http_request_body(name:)
5✔
48
        super do |xml|
932✔
49
          xml.args do
932✔
50
            xml.id id
932✔
51
          end
52
        end
53
      end
54

55
      def parse_note(note, metadata)
5✔
56
        if note.count > 0
458✔
UNCOV
57
          metadata[:mf_note] = note.text
1✔
58
        end
59
      end
60

61
      def parse_image(image, metadata)
5✔
62
        if image.count > 0
458✔
63
          metadata[:image_size] = image.xpath("./width").text + " X " + image.xpath("./height").text
×
64
        end
65
      end
66

67
      def parse_quota(quota, metadata)
5✔
68
        metadata[:quota_allocation] = quota.xpath("./allocation/@h").text
458✔
69
        metadata[:quota_allocation_raw] = quota.xpath("./allocation").text.to_i
458✔
70
        metadata[:quota_used] = quota.xpath("./used/@h").text
458✔
71
        metadata[:quota_used_raw] = quota.xpath("./used").text.to_i
458✔
72
      end
73

74
      # Update this to match full 0.6.1 schema
75
      def parse(asset)
5✔
76
        {
77
          id: asset.xpath("./@id").text,
458✔
78
          name: asset.xpath("./name").text,
79
          creator: asset.xpath("./creator/user").text,
80
          description: asset.xpath("./description").text,
81
          collection: asset.xpath("./@collection")&.text == "true",
82
          path: asset.xpath("./path").text,
83
          type: asset.xpath("./type").text,
84
          namespace: asset.xpath("./namespace").text,
85
          accumulators: asset.xpath("./collection/accumulator/value") # list of accumulator values in xml format. Can parse further through xpath
86
        }.merge(parse_project(asset.xpath("//tigerdata:project", "tigerdata" => "tigerdata").first, asset))
87
      end
88

89
      # rubocop:disable Metrics/MethodLength
90
      def parse_project(project, asset)
5✔
91
        return {} if project.blank?
458✔
92
        metadata = {
93
          description: project.xpath("./Description").text,
453✔
94
          data_sponsor: project.xpath("./DataSponsor").text,
95
          data_manager: project.xpath("./DataManager").text,
96
          departments: project.xpath("./Department").children.map(&:text),
97
          project_directory: project.xpath("./ProjectDirectory").text,
98
          project_id: project.xpath("./ProjectID").text,
99
          submission: parse_submission(project),
100
          title: project.xpath("./Title").text,
101
          project_purpose: project.xpath("./ProjectPurpose").text
102
        }
103
        metadata.merge!(parse_data_users(asset, project))
453✔
104
        metadata.merge!(parse_project_dates(project))
453✔
105
        metadata.merge!(parse_storage_options(project))
453✔
106
      end
107
      # rubocop:enable Metrics/MethodLength
108

109
      # NOTE: We are still using the "DataUser" attribute in the Project Metadata
110
      # to drive the list of users who can read and/or write to the Mediaflux
111
      # asset. In the future we could go by the ACL information in the asset
112
      # alone (and bypass the DataUser attribute) and that will be more accurate.
113
      def parse_data_users(asset, project)
5✔
114
        data_users = data_users_from_string(project.xpath("./DataUser").text)
453✔
115
        rw_users = parse_read_write_users(asset, data_users)
453✔
116
        {
117
          rw_users: rw_users,
453✔
118
          ro_users: data_users - rw_users
119
        }
120
      end
121

122
      def parse_project_dates(project)
5✔
123
        {
124
          created_by: project.xpath("./CreatedBy").text,
453✔
125
          created_on: project.xpath("./CreatedOn").text,
126
          updated_by: project.xpath("./UpdatedBy").text,
127
          updated_on: project.xpath("./UpdatedOn").text
128
        }
129
      end
130

131
      def parse_storage_options(project)
5✔
132
        {
133
          number_of_files: project.xpath("./NumberofFiles").text,
453✔
134
          hpc: project.xpath("./Hpc").text == "true",
135
          smb: project.xpath("./Smb").text == "true",
136
          globus: project.xpath("./Globus").text == "true"
137
        }
138
      end
139

140
      def parse_submission(project)
5✔
141
        submission = project.xpath("./Submission")
453✔
142
        {
143
          requested_by: submission.xpath("./RequestedBy").text,
453✔
144
          requested_on: submission.xpath("./RequestDateTime").text,
145
          approved_by: submission.xpath("./ApprovedBy").text,
146
          approved_on: submission.xpath("./ApprovalDateTime").text
147
        }
148
      end
149

150
      def data_users_from_string(users)
5✔
151
        return [] if users.blank?
453✔
152
        users.split(",").compact_blank
66✔
153
      end
154

155
      # Calculates which of the `data_users` listed in the metadata have
156
      # write access in Mediaflux for the given asset.
157
      def parse_read_write_users(asset, data_users)
5✔
158
        users = asset.xpath("./acl").map do |acl|
453✔
159
          uid = acl.xpath("./actor").text.gsub("princeton:", "")
694✔
160
          if data_users.include?(uid) && acl.xpath("./metadata").map(&:text).include?("write")
694✔
UNCOV
161
            uid
15✔
162
          else
163
            ""
679✔
164
          end
165
        end
166
        users.compact_blank
453✔
167
      end
168
  end
169
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc