diff --git a/.gitignore b/.gitignore index 922d62185..e9cd5d0d2 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,6 @@ migration/test* *.sublime-workspace # Rbenv artifacts -.ruby-version test/test_run.log # Debug output diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 000000000..9cec7165a --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.1.6 diff --git a/Dockerfile b/Dockerfile index bbc327ceb..b9529fd14 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,36 @@ -ARG RUBY_VERSION -ARG DISTRO_NAME=bullseye +ARG RUBY_VERSION=3.1 +ARG DISTRO=bullseye -FROM ruby:$RUBY_VERSION-$DISTRO_NAME +FROM ruby:$RUBY_VERSION-$DISTRO -RUN apt-get update -yqq && apt-get install -yqq --no-install-recommends \ - openjdk-11-jre-headless \ - raptor2-utils \ - && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + git \ + libxml2 \ + libxslt-dev \ + openjdk-11-jre-headless \ + raptor2-utils \ + && rm -rf /var/lib/apt/lists/* -RUN mkdir -p /srv/ontoportal/ontologies_linked_data -RUN mkdir -p /srv/ontoportal/bundle -COPY Gemfile* /srv/ontoportal/ontologies_linked_data/ +WORKDIR /app -WORKDIR /srv/ontoportal/ontologies_linked_data +COPY Gemfile* *.gemspec ./ -RUN gem update --system -RUN gem install bundler -ENV BUNDLE_PATH=/srv/ontoportal/bundle -RUN bundle install +# Copy only the `version.rb` file to prevent missing file errors! +COPY lib/ontologies_linked_data/version.rb lib/ontologies_linked_data/ -COPY . /srv/ontoportal/ontologies_linked_data +#Install the exact Bundler version from Gemfile.lock (if it exists) +RUN gem update --system && \ + if [ -f Gemfile.lock ]; then \ + BUNDLER_VERSION=$(grep -A 1 "BUNDLED WITH" Gemfile.lock | tail -n 1 | tr -d ' '); \ + gem install bundler -v "$BUNDLER_VERSION"; \ + else \ + gem install bundler; \ + fi + +RUN bundle config set --global no-document 'true' +RUN bundle install --jobs 4 --retry 3 + +COPY . ./ + +CMD ["bundle", "exec", "rake"] diff --git a/Gemfile b/Gemfile index c20d9ca0d..11561bdb7 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,7 @@ source 'https://rubygems.org' +gemspec + gem 'activesupport', '~> 4' gem 'addressable', '~> 2.8' gem 'bcrypt', '~> 3.0' @@ -37,6 +39,5 @@ end gem 'goo', github: 'ncbo/goo', branch: 'master' gem 'sparql-client', github: 'ncbo/sparql-client', branch: 'master' -gem 'net-ftp' gem 'public_suffix', '~> 5.1.1' -gem 'net-imap', '~> 0.4.18' \ No newline at end of file +gem 'net-imap', '~> 0.4.18' diff --git a/Gemfile.lock b/Gemfile.lock index b5e13479a..fd4e234dd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/ncbo/goo.git - revision: 39f67ab7fae7675b6ff417ace0ab923e40ffcbcd + revision: b9019ad9e1eb78c74105fc6c6a879085066da17d branch: master specs: goo (0.0.2) @@ -24,6 +24,25 @@ GIT net-http-persistent (= 2.9.4) rdf (>= 1.0) +PATH + remote: . + specs: + ontologies_linked_data (0.0.1) + activesupport + bcrypt + goo + json + libxml-ruby + multi_json + net-ftp + oj + omni_logger + pony + rack + rack-test + rsolr + rubyzip + GEM remote: https://rubygems.org/ specs: @@ -36,7 +55,7 @@ GEM addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) ansi (1.5.0) - ast (2.4.2) + ast (2.4.3) bcrypt (3.1.20) bigdecimal (3.1.9) builder (3.3.0) @@ -44,7 +63,7 @@ GEM logger (~> 1.5) coderay (1.1.3) concurrent-ruby (1.3.5) - connection_pool (2.5.0) + connection_pool (2.5.3) cube-ruby (0.0.3) daemons (1.4.1) date (3.4.1) @@ -55,15 +74,15 @@ GEM launchy (>= 2.1, < 4.0) mail (~> 2.7) eventmachine (1.2.7) - faraday (2.12.2) + faraday (2.13.1) faraday-net_http (>= 2.0, < 3.5) json logger faraday-net_http (3.4.0) net-http (>= 0.5.0) - ffi (1.17.1-aarch64-linux-gnu) - ffi (1.17.1-arm64-darwin) - ffi (1.17.1-x86_64-linux-gnu) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-linux-gnu) hashie (5.0.0) htmlentities (4.3.4) http-accept (1.7.0) @@ -71,15 +90,16 @@ GEM domain_name (~> 0.5) i18n (0.9.5) concurrent-ruby (~> 1.0) - json (2.10.1) + json (2.11.3) json_pure (2.8.1) language_server-protocol (3.17.0.4) - launchy (3.1.0) + launchy (3.1.1) addressable (~> 2.8) childprocess (~> 5.0) logger (~> 1.6) libxml-ruby (5.0.3) - logger (1.6.5) + lint_roller (1.1.0) + logger (1.7.0) macaddr (1.7.2) systemu (~> 2.6.5) mail (2.8.1) @@ -88,10 +108,10 @@ GEM net-pop net-smtp method_source (1.1.0) - mime-types (3.6.0) + mime-types (3.6.2) logger mime-types-data (~> 3.2015) - mime-types-data (3.2025.0204) + mime-types-data (3.2025.0422) mini_mime (1.1.5) minitest (4.7.5) minitest-reporters (0.14.24) @@ -106,7 +126,7 @@ GEM net-http (0.6.0) uri net-http-persistent (2.9.4) - net-imap (0.4.19) + net-imap (0.4.21) date net-protocol net-pop (0.1.2) @@ -116,35 +136,36 @@ GEM net-smtp (0.5.1) net-protocol netrc (0.11.0) - oj (3.16.9) + oj (3.16.10) bigdecimal (>= 3.0) ostruct (>= 0.2) omni_logger (0.1.4) logger ostruct (0.6.1) - parallel (1.26.3) - parser (3.3.7.1) + parallel (1.27.0) + parser (3.3.8.0) ast (~> 2.4.1) racc pony (1.13.1) mail (>= 2.0) powerbar (2.0.1) hashie (>= 1.1.0) + prism (1.4.0) pry (0.15.2) coderay (~> 1.1) method_source (~> 1.0) public_suffix (5.1.1) racc (1.8.1) - rack (2.2.11) + rack (2.2.13) rack-test (0.8.3) rack (>= 1.0, < 3) rainbow (3.1.1) rake (10.5.0) rdf (1.0.8) addressable (>= 2.2) - redis (5.3.0) + redis (5.4.0) redis-client (>= 0.22.0) - redis-client (0.23.2) + redis-client (0.24.0) connection_pool regexp_parser (2.10.0) request_store (1.7.0) @@ -154,22 +175,24 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - rexml (3.4.0) + rexml (3.4.1) rsolr (2.6.0) builder (>= 2.1.2) faraday (>= 0.9, < 3, != 2.0.0) - rubocop (1.71.2) + rubocop (1.75.4) json (~> 2.3) - language_server-protocol (>= 3.17.0) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.38.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.38.0) - parser (>= 3.3.1.0) + rubocop-ast (1.44.1) + parser (>= 3.3.7.2) + prism (~> 1.4) ruby-progressbar (1.13.0) rubyzip (1.3.0) simplecov (0.22.0) @@ -194,7 +217,7 @@ GEM unicode-display_width (3.1.4) unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (4.0.4) - uri (1.0.2) + uri (1.0.3) uuid (2.3.9) macaddr (~> 1.0) @@ -202,7 +225,6 @@ PLATFORMS aarch64-linux arm64-darwin-22 arm64-darwin-23 - arm64-darwin-24 x86_64-linux DEPENDENCIES @@ -217,10 +239,10 @@ DEPENDENCIES minitest (~> 4) minitest-reporters (>= 0.5.0) multi_json (~> 1.0) - net-ftp net-imap (~> 0.4.18) oj (~> 3.0) omni_logger + ontologies_linked_data! pony pry public_suffix (~> 5.1.1) @@ -238,4 +260,4 @@ DEPENDENCIES thin BUNDLED WITH - 2.4.22 + 2.6.3 diff --git a/bin/owlapi-wrapper-1.4.2.jar b/bin/owlapi-wrapper-1.4.2.jar old mode 100644 new mode 100755 diff --git a/config/schemes/ontology_submission.yml b/config/schemes/ontology_submission.yml index 6086bc693..750202048 100644 --- a/config/schemes/ontology_submission.yml +++ b/config/schemes/ontology_submission.yml @@ -21,10 +21,10 @@ #Acronym => Ontology object (omv:acronym) #Name => Ontology object (omv:name) -#URI -URI: +#uri +uri: display: "general" - label: "URI" + label: "uri" helpText: "The URI of the ontology which is described by these metadata." example: 'https://w3id.org/myontology' description: [ @@ -175,7 +175,7 @@ hasOntologySyntax: naturalLanguage: display: "general" label: "Natural language" - helpText: "The language of the content of the ontology (with values in Lexvo/iso639-1)." + helpText: "The language of the content of the ontology (with values in ISO 639-1)." description: [ "DCTERMS: A language of the resource. Recommended practice is to use either a non-literal value representing a language from a controlled vocabulary such as ISO 639-2 or ISO 639-3, or a literal value consisting of an IETF Best Current Practice 47 language tag.", "OMV: The language of the content of the ontology, i.e. English, French, etc.", @@ -183,20 +183,57 @@ naturalLanguage: "SCHEMA: The language of the content or performance or used in an action. Please use one of the language codes from the IETF BCP 47 standard." ] extractedMetadata: true enforcedValues: { - "http://lexvo.org/id/iso639-1/en": "English", - "http://lexvo.org/id/iso639-1/fr": "French", - "http://lexvo.org/id/iso639-1/es": "Spanish", - "http://lexvo.org/id/iso639-1/pt": "Portuguese", - "http://lexvo.org/id/iso639-1/it": "Italian", - "http://lexvo.org/id/iso639-1/de": "German", - "http://lexvo.org/id/iso639-1/ar": "Arabic", - "http://lexvo.org/id/iso639-1/zh": "Chinese", - "http://lexvo.org/id/iso639-1/hi": "Hindi", - "http://lexvo.org/id/iso639-1/nl": "Dutch", - "http://lexvo.org/id/iso639-1/fi": "Finnish", - "http://lexvo.org/id/iso639-1/el": "Greek", - "http://lexvo.org/id/iso639-1/ja": "Japanese", - "http://lexvo.org/id/iso639-1/pt-br": "Brazilian" + "ar": "Arabic", + "az": "Azerbaijani", + "be": "Belarusian", + "bn": "Bengali", + "cs": "Czech", + "da": "Danish", + "de": "German", + "el": "Greek", + "en": "English", + "es": "Spanish", + "et": "Estonian", + "eu": "Basque", + "fa": "Persian", + "fi": "Finnish", + "fr": "French", + "he": "Hebrew", + "hi": "Hindi", + "hr": "Croatian", + "hu": "Hungarian", + "id": "Indonesian", + "it": "Italian", + "ja": "Japanese", + "ka": "Georgian", + "kk": "Kazakh", + "ko": "Korean", + "lt": "Lithuanian", + "lv": "Latvian", + "mn": "Mongolian", + "ms": "Malay", + "my": "Burmese", + "nl": "Dutch", + "no": "Norwegian", + "pa": "Punjabi", + "pl": "Polish", + "pt": "Portuguese", + "pt-br": "Brazilian Portuguese", + "ro": "Romanian", + "ru": "Russian", + "sk": "Slovak", + "sl": "Slovenian", + "sr": "Serbian", + "sv": "Swedish", + "sw": "Swahili", + "ta": "Tamil", + "te": "Telugu", + "th": "Thai", + "tr": "Turkish", + "uk": "Ukrainian", + "uz": "Uzbek", + "vi": "Vietnamese", + "zh": "Chinese" } metadataMappings: [ "omv:naturalLanguage", "dc:language", "dcterms:language", "doap:language", "schema:inLanguage" ] diff --git a/docker-compose.yml b/docker-compose.yml index a63c268f1..257ed02d8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,12 +4,8 @@ x-app: &app args: RUBY_VERSION: '3.1' # Increase the version number in the image tag every time Dockerfile or its arguments is changed - image: ontologies_ld-dev:0.0.3 + image: ontologies_ld-dev:0.0.4 environment: &env - # default bundle config resolves to /usr/local/bundle/config inside of the container - # we are setting it to local app directory if we need to use 'bundle config local' - BUNDLE_APP_CONFIG: /srv/ontoportal/ontologies_api/.bundle - BUNDLE_PATH: /srv/ontoportal/bundle COVERAGE: 'true' # enable simplecov code coverage REDIS_HOST: redis-ut REDIS_PORT: 6379 @@ -20,8 +16,8 @@ x-app: &app command: /bin/bash volumes: # bundle volume for hosting gems installed by bundle; it speeds up gem install in local development - - bundle:/srv/ontoportal/bundle - - .:/srv/ontoportal/ontologies_linked_data + - bundle:/usr/local/bundle + - .:/app # mount directory containing development version of the gems if you need to use 'bundle config local' #- /Users/alexskr/ontoportal:/Users/alexskr/ontoportal depends_on: &depends_on @@ -117,7 +113,7 @@ services: retries: 5 agraph-ut: - image: franzinc/agraph:v8.1.0 + image: franzinc/agraph:v8.3.1 platform: linux/amd64 environment: - AGRAPH_SUPER_USER=test diff --git a/lib/ontologies_linked_data/models/ontology_submission.rb b/lib/ontologies_linked_data/models/ontology_submission.rb index 305709dd9..b19a18534 100644 --- a/lib/ontologies_linked_data/models/ontology_submission.rb +++ b/lib/ontologies_linked_data/models/ontology_submission.rb @@ -18,6 +18,9 @@ class OntologySubmission < LinkedData::Models::Base extend LinkedData::Concerns::OntologySubmission::DefaultCallbacks FLAT_ROOTS_LIMIT = 1000 + # default file permissions for files copied from tempdir + REPOSITORY_FILE_MODE = 0o660 # rw-rw---- + REPOSITORY_DIR_MODE = 0o2770 # rwxrws--- + set-GID model :ontology_submission, scheme: File.join(__dir__, '../../../config/schemes/ontology_submission.yml'), name_with: ->(s) { submission_id_generator(s) } @@ -39,8 +42,7 @@ class OntologySubmission < LinkedData::Models::Base # Ontology metadata # General metadata - attribute :uri, namespace: :omv, type: :uri, enforce: %i[distinct_of_identifier], fuzzy_search: true - attribute :versionIRI, namespace: :owl, type: :uri, enforce: [:distinct_of_URI] + attribute :versionIRI, namespace: :owl, type: :uri, enforce: [:distinct_of_uri] attribute :version, namespace: :omv attribute :status, namespace: :omv, default: ->(x) { 'production' } attribute :deprecated, namespace: :owl, type: :boolean, default: ->(x) { false } @@ -49,18 +51,25 @@ class OntologySubmission < LinkedData::Models::Base attribute :hasOntologySyntax, namespace: :omv, type: :uri, default: ->(s) { ontology_syntax_default(s) } attribute :naturalLanguage, namespace: :omv, type: %i[list] attribute :isOfType, namespace: :omv, type: :uri - attribute :identifier, namespace: :dct, type: %i[list uri], enforce: [:distinct_of_URI] + attribute :identifier, namespace: :dct, type: %i[list uri], enforce: [:distinct_of_uri] # Description metadata attribute :description, namespace: :omv, enforce: %i[concatenate], fuzzy_search: true + + # attribute :homepage + # attribute :documentation, namespace: :omv + # attribute :publication + # attribute :uri, namespace: :omv attribute :homepage, namespace: :foaf, type: :uri attribute :documentation, namespace: :omv, type: :uri + attribute :publication, type: %i[uri list] + attribute :uri, namespace: :omv, type: :uri, enforce: %i[distinct_of_identifier], fuzzy_search: true + attribute :notes, namespace: :omv, type: :list attribute :keywords, namespace: :omv, type: :list attribute :hiddenLabel, namespace: :skos, type: :list attribute :alternative, namespace: :dct, type: :list attribute :abstract, namespace: :dct - attribute :publication, type: %i[uri list] # Licensing metadata attribute :hasLicense, namespace: :omv, type: :uri @@ -274,23 +283,33 @@ def self.submission_id_generator(ss) ) end - def self.copy_file_repository(acronym, submissionId, src, filename = nil) - path_to_repo = File.join([LinkedData.settings.repository_folder, acronym.to_s, submissionId.to_s]) - name = filename || File.basename(File.new(src).path) - # THIS LOGGER IS JUST FOR DEBUG - remove after NCBO-795 is closed - # https://github.com/ncbo/bioportal-project/issues/323 - # logger = Logger.new(Dir.pwd + "/logs/create_permissions.log") - if not Dir.exist? path_to_repo - FileUtils.mkdir_p path_to_repo - # logger.debug("Dir created #{path_to_repo} | #{"%o" % File.stat(path_to_repo).mode} | umask: #{File.umask}") # NCBO-795 - end - dst = File.join([path_to_repo, name]) - FileUtils.copy(src, dst) - # logger.debug("File created #{dst} | #{"%o" % File.stat(dst).mode} | umask: #{File.umask}") # NCBO-795 - if not File.exist? dst - raise Exception, "Unable to copy #{src} to #{dst}" + def self.copy_file_repository(acronym, submission_id, src, filename = nil) + path_to_repo = File.join( + LinkedData.settings.repository_folder, + acronym.to_s, + submission_id.to_s + ) + + name = filename || File.basename(src) + dst = File.join(path_to_repo, name) + + begin + FileUtils.mkdir_p(path_to_repo) + FileUtils.chmod(REPOSITORY_DIR_MODE, path_to_repo) + + FileUtils.copy(src, dst) + # Uploaded files are initially written to a Tempfile in tmpdir with + # permissions 0600 (owner read/write only) for security. To ensure + # repository files are also accessible by the service group as intended, + # we explicitly chmod the destination file to REPOSITORY_FILE_MODE. + FileUtils.chmod(REPOSITORY_FILE_MODE, dst) + + raise "Unable to copy #{src} to #{dst}" unless File.exist?(dst) + + dst + rescue StandardError => e + raise "Failed to copy #{src} to #{dst}: [#{e.class}] #{e.message}" end - return dst end def valid? @@ -356,9 +375,10 @@ def sanity_check self.errors[:uploadFilePath] = ["In non-summary only submissions a data file or url must be provided."] return false elsif self.pullLocation - self.errors[:pullLocation] = ["File at #{self.pullLocation.to_s} does not exist"] if self.uploadFilePath.nil? - return remote_file_exists?(self.pullLocation.to_s) + remote_exists = remote_file_exists?(self.pullLocation.to_s) + self.errors[:pullLocation] = ["File at #{self.pullLocation.to_s} does not exist"] unless remote_exists + return remote_exists end return true end diff --git a/lib/ontologies_linked_data/services/submission_process/submission_processor.rb b/lib/ontologies_linked_data/services/submission_process/submission_processor.rb index 319c20587..9e922494c 100644 --- a/lib/ontologies_linked_data/services/submission_process/submission_processor.rb +++ b/lib/ontologies_linked_data/services/submission_process/submission_processor.rb @@ -56,6 +56,8 @@ def process_submission(logger, options = {}) @submission.index_properties(logger, commit: index_commit) end + @submission.generate_diff(logger) if diff + if run_metrics unless parsed raise StandardError, "Metrics cannot be generated on the submission @@ -64,7 +66,6 @@ def process_submission(logger, options = {}) end @submission.generate_metrics(logger) end - @submission.generate_diff(logger) if diff end @submission.save diff --git a/ontologies_linked_data.gemspec b/ontologies_linked_data.gemspec index c3443aff6..cdc98518b 100644 --- a/ontologies_linked_data.gemspec +++ b/ontologies_linked_data.gemspec @@ -1,33 +1,37 @@ # -*- encoding: utf-8 -*- -require File.expand_path('../lib/ontologies_linked_data/version', __FILE__) +require_relative 'lib/ontologies_linked_data/version' Gem::Specification.new do |gem| + gem.name = "ontologies_linked_data" + gem.version = LinkedData::VERSION + gem.summary = "Models and serializers for ontologies and related artifacts backed by an RDF database" + gem.summary = "This library can be used for interacting with an AllegroGraph or 4store instance that stores " \ + "BioPortal-based ontology information. Models in the library are based on Goo. Serializers " \ + "support RDF serialization as Rack Middleware and automatic generation of hypermedia links." gem.authors = ["Paul R Alexander"] - gem.email = ["palexander@stanford.edu"] - gem.description = %q{Models and serializers for ontologies and related artifacts backed by 4store} - gem.summary = %q{This library can be used for interacting with a 4store instance that stores NCBO-based ontology information. Models in the library are based on Goo. Serializers support RDF serialization as Rack Middleware and automatic generation of hypermedia links.} + gem.email = ["support@bioontology.org"] gem.homepage = "https://github.com/ncbo/ontologies_linked_data" - gem.files = `git ls-files`.split($\) - gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } - gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) - gem.name = "ontologies_linked_data" + gem.files = %x(git ls-files).split("\n") + gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) } gem.require_paths = ["lib"] - gem.version = LinkedData::VERSION + gem.required_ruby_version = ">= 3.1" + + gem.add_dependency("activesupport") + gem.add_dependency("bcrypt") gem.add_dependency("goo") gem.add_dependency("json") + gem.add_dependency("libxml-ruby") gem.add_dependency("multi_json") + gem.add_dependency("net-ftp") gem.add_dependency("oj") - gem.add_dependency("bcrypt") + gem.add_dependency("omni_logger") + gem.add_dependency("pony") gem.add_dependency("rack") gem.add_dependency("rack-test") - gem.add_dependency("rubyzip") - gem.add_dependency("libxml-ruby") - gem.add_dependency("activesupport") gem.add_dependency("rsolr") - gem.add_dependency("pony") - gem.add_dependency("omni_logger") + gem.add_dependency("rubyzip") gem.add_development_dependency("email_spec") diff --git a/test/models/test_ontology_submission.rb b/test/models/test_ontology_submission.rb index a233c74ec..79ae72fe4 100644 --- a/test/models/test_ontology_submission.rb +++ b/test/models/test_ontology_submission.rb @@ -1224,4 +1224,37 @@ def test_submission_delete_remove_files sub.delete assert !Dir.exist?(data_folder) end + + def test_copy_file_repository_from_tempfile + # Simulate a Rack Tempfile upload from tmpdir; + # tmpfile get 0600 permission and we need 660 for the copy to repository + fixture = "./test/data/ontology_files/BRO_v3.2.owl" + tmp = Tempfile.new(["upload", ".owl"]) + begin + FileUtils.cp(fixture, tmp.path) + tmp.close + + # Assert the source Tempfile has default 0600 permissions + # `& 0o777` is a bitwise AND that out all non-permission bits + # convers 0o100600 (regular file with owner rw) to 0600 + src_mode = File.stat(tmp.path).mode & 0o0777 + assert_equal 0o0600, src_mode + + dst = LinkedData::Models::OntologySubmission + .copy_file_repository("TMPTEST", 99, tmp.path) + + repo_root = LinkedData.settings.repository_folder + assert_match( + %r{\A#{Regexp.escape(repo_root)}/TMPTEST/99/}, + dst, + "Expected file to be copied into #{repo_root}/TMPTEST/99/" + ) + assert File.exist?(dst), "Destination file should exist" + + mode = File.stat(dst).mode & 0o0777 + assert_equal 0o0660, mode, format("Expected file mode 0660, got %o", mode) + ensure + tmp.unlink + end + end end