From 802ad84d30a132c8ce37b7e3320eb9f8ba9f7abe Mon Sep 17 00:00:00 2001 From: Lysann Tranvouez Date: Sun, 8 Mar 2026 22:56:21 +0100 Subject: [PATCH] include repo as text fixture, no need to clone from actual github --- pass.xcodeproj/project.pbxproj | 4 + .../Fixtures/password-store.git/FETCH_HEAD | 1 + passKitTests/Fixtures/password-store.git/HEAD | 1 + .../Fixtures/password-store.git/config | 9 + .../Fixtures/password-store.git/description | 1 + .../hooks/applypatch-msg.sample | 15 ++ .../hooks/commit-msg.sample | 24 +++ .../hooks/fsmonitor-watchman.sample | 174 ++++++++++++++++++ .../hooks/post-update.sample | 8 + .../hooks/pre-applypatch.sample | 14 ++ .../hooks/pre-commit.sample | 49 +++++ .../hooks/pre-merge-commit.sample | 13 ++ .../password-store.git/hooks/pre-push.sample | 53 ++++++ .../hooks/pre-rebase.sample | 169 +++++++++++++++++ .../hooks/pre-receive.sample | 24 +++ .../hooks/prepare-commit-msg.sample | 42 +++++ .../hooks/push-to-checkout.sample | 78 ++++++++ .../password-store.git/hooks/update.sample | 128 +++++++++++++ .../Fixtures/password-store.git/info/exclude | 6 + ...8dbb253e7642cc425de97363624aab04882615.idx | Bin 0 -> 2724 bytes ...dbb253e7642cc425de97363624aab04882615.pack | Bin 0 -> 15331 bytes .../Fixtures/password-store.git/packed-refs | 2 + .../refs/remotes/origin/master | 1 + passKitTests/Models/PasswordStoreTest.swift | 3 +- plans/01-improve-test-coverage-plan.md | 53 ++++-- 25 files changed, 852 insertions(+), 20 deletions(-) create mode 100644 passKitTests/Fixtures/password-store.git/FETCH_HEAD create mode 100644 passKitTests/Fixtures/password-store.git/HEAD create mode 100644 passKitTests/Fixtures/password-store.git/config create mode 100644 passKitTests/Fixtures/password-store.git/description create mode 100755 passKitTests/Fixtures/password-store.git/hooks/applypatch-msg.sample create mode 100755 passKitTests/Fixtures/password-store.git/hooks/commit-msg.sample create mode 100755 passKitTests/Fixtures/password-store.git/hooks/fsmonitor-watchman.sample create mode 100755 passKitTests/Fixtures/password-store.git/hooks/post-update.sample create mode 100755 passKitTests/Fixtures/password-store.git/hooks/pre-applypatch.sample create mode 100755 passKitTests/Fixtures/password-store.git/hooks/pre-commit.sample create mode 100755 passKitTests/Fixtures/password-store.git/hooks/pre-merge-commit.sample create mode 100755 passKitTests/Fixtures/password-store.git/hooks/pre-push.sample create mode 100755 passKitTests/Fixtures/password-store.git/hooks/pre-rebase.sample create mode 100755 passKitTests/Fixtures/password-store.git/hooks/pre-receive.sample create mode 100755 passKitTests/Fixtures/password-store.git/hooks/prepare-commit-msg.sample create mode 100755 passKitTests/Fixtures/password-store.git/hooks/push-to-checkout.sample create mode 100755 passKitTests/Fixtures/password-store.git/hooks/update.sample create mode 100644 passKitTests/Fixtures/password-store.git/info/exclude create mode 100644 passKitTests/Fixtures/password-store.git/objects/pack/pack-6a8dbb253e7642cc425de97363624aab04882615.idx create mode 100644 passKitTests/Fixtures/password-store.git/objects/pack/pack-6a8dbb253e7642cc425de97363624aab04882615.pack create mode 100644 passKitTests/Fixtures/password-store.git/packed-refs create mode 100644 passKitTests/Fixtures/password-store.git/refs/remotes/origin/master diff --git a/pass.xcodeproj/project.pbxproj b/pass.xcodeproj/project.pbxproj index ed9df0d..e5522cb 100644 --- a/pass.xcodeproj/project.pbxproj +++ b/pass.xcodeproj/project.pbxproj @@ -114,6 +114,7 @@ 5F9D7B0D27AF6F7500A8AB22 /* CryptoTokenKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F9D7B0C27AF6F7300A8AB22 /* CryptoTokenKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 5F9D7B0E27AF6FCA00A8AB22 /* CryptoTokenKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F9D7B0C27AF6F7300A8AB22 /* CryptoTokenKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 5F9D7B0F27AF6FD200A8AB22 /* CryptoTokenKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F9D7B0C27AF6F7300A8AB22 /* CryptoTokenKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + 8AD8EBF32F5E2723007475AB /* Fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 8AD8EBF22F5E268D007475AB /* Fixtures */; }; 9A1D1CE526E5D1CE0052028E /* OneTimePassword in Frameworks */ = {isa = PBXBuildFile; productRef = 9A1D1CE426E5D1CE0052028E /* OneTimePassword */; }; 9A1D1CE726E5D2230052028E /* OneTimePassword in Frameworks */ = {isa = PBXBuildFile; productRef = 9A1D1CE626E5D2230052028E /* OneTimePassword */; }; 9A1F47FA26E5CF4B000C0E01 /* OneTimePassword in Frameworks */ = {isa = PBXBuildFile; productRef = 9A1F47F926E5CF4B000C0E01 /* OneTimePassword */; }; @@ -422,6 +423,7 @@ 30F6C1B327664C7200BE5AB2 /* SVProgressHUD.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = SVProgressHUD.xcframework; path = Carthage/Build/SVProgressHUD.xcframework; sourceTree = ""; }; 30FD2F77214D9E0E005E0A92 /* ParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParserTest.swift; sourceTree = ""; }; 5F9D7B0C27AF6F7300A8AB22 /* CryptoTokenKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CryptoTokenKit.framework; path = System/Library/Frameworks/CryptoTokenKit.framework; sourceTree = SDKROOT; }; + 8AD8EBF22F5E268D007475AB /* Fixtures */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Fixtures; sourceTree = ""; }; 9A1EF0B324C50DD80074FEAC /* passBeta.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = passBeta.entitlements; sourceTree = ""; }; 9A1EF0B424C50E780074FEAC /* passBetaAutoFillExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = passBetaAutoFillExtension.entitlements; sourceTree = ""; }; 9A1EF0B524C50EE00074FEAC /* passBetaExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = passBetaExtension.entitlements; sourceTree = ""; }; @@ -883,6 +885,7 @@ 30A86F93230F235800F821A4 /* Crypto */, 30BAC8C322E3BA4300438475 /* Testbase */, 30697C5521F63F870064FCAC /* Extensions */, + 8AD8EBF22F5E268D007475AB /* Fixtures */, 301F6464216164670071A4CE /* Helpers */, 30C015A7214ED378005BB6DF /* Models */, 30C015A6214ED32A005BB6DF /* Parser */, @@ -1427,6 +1430,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8AD8EBF32F5E2723007475AB /* Fixtures in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/passKitTests/Fixtures/password-store.git/FETCH_HEAD b/passKitTests/Fixtures/password-store.git/FETCH_HEAD new file mode 100644 index 0000000..ef06926 --- /dev/null +++ b/passKitTests/Fixtures/password-store.git/FETCH_HEAD @@ -0,0 +1 @@ +925eb0f6b19282b5f10dfe008e0062b4be6dd41a not-for-merge branch 'master' of https://github.com/mssun/passforios-password-store diff --git a/passKitTests/Fixtures/password-store.git/HEAD b/passKitTests/Fixtures/password-store.git/HEAD new file mode 100644 index 0000000..cb089cd --- /dev/null +++ b/passKitTests/Fixtures/password-store.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/passKitTests/Fixtures/password-store.git/config b/passKitTests/Fixtures/password-store.git/config new file mode 100644 index 0000000..876f087 --- /dev/null +++ b/passKitTests/Fixtures/password-store.git/config @@ -0,0 +1,9 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + ignorecase = true + precomposeunicode = true +[remote "origin"] + url = https://github.com/mssun/passforios-password-store.git + fetch = +refs/heads/*:refs/remotes/origin/* diff --git a/passKitTests/Fixtures/password-store.git/description b/passKitTests/Fixtures/password-store.git/description new file mode 100644 index 0000000..9258d15 --- /dev/null +++ b/passKitTests/Fixtures/password-store.git/description @@ -0,0 +1 @@ +Example password store repository for passforios tests. diff --git a/passKitTests/Fixtures/password-store.git/hooks/applypatch-msg.sample b/passKitTests/Fixtures/password-store.git/hooks/applypatch-msg.sample new file mode 100755 index 0000000..a5d7b84 --- /dev/null +++ b/passKitTests/Fixtures/password-store.git/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/passKitTests/Fixtures/password-store.git/hooks/commit-msg.sample b/passKitTests/Fixtures/password-store.git/hooks/commit-msg.sample new file mode 100755 index 0000000..b58d118 --- /dev/null +++ b/passKitTests/Fixtures/password-store.git/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/passKitTests/Fixtures/password-store.git/hooks/fsmonitor-watchman.sample b/passKitTests/Fixtures/password-store.git/hooks/fsmonitor-watchman.sample new file mode 100755 index 0000000..23e856f --- /dev/null +++ b/passKitTests/Fixtures/password-store.git/hooks/fsmonitor-watchman.sample @@ -0,0 +1,174 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use IPC::Open2; + +# An example hook script to integrate Watchman +# (https://facebook.github.io/watchman/) with git to speed up detecting +# new and modified files. +# +# The hook is passed a version (currently 2) and last update token +# formatted as a string and outputs to stdout a new update token and +# all files that have been modified since the update token. Paths must +# be relative to the root of the working tree and separated by a single NUL. +# +# To enable this hook, rename this file to "query-watchman" and set +# 'git config core.fsmonitor .git/hooks/query-watchman' +# +my ($version, $last_update_token) = @ARGV; + +# Uncomment for debugging +# print STDERR "$0 $version $last_update_token\n"; + +# Check the hook interface version +if ($version ne 2) { + die "Unsupported query-fsmonitor hook version '$version'.\n" . + "Falling back to scanning...\n"; +} + +my $git_work_tree = get_working_dir(); + +my $retry = 1; + +my $json_pkg; +eval { + require JSON::XS; + $json_pkg = "JSON::XS"; + 1; +} or do { + require JSON::PP; + $json_pkg = "JSON::PP"; +}; + +launch_watchman(); + +sub launch_watchman { + my $o = watchman_query(); + if (is_work_tree_watched($o)) { + output_result($o->{clock}, @{$o->{files}}); + } +} + +sub output_result { + my ($clockid, @files) = @_; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # binmode $fh, ":utf8"; + # print $fh "$clockid\n@files\n"; + # close $fh; + + binmode STDOUT, ":utf8"; + print $clockid; + print "\0"; + local $, = "\0"; + print @files; +} + +sub watchman_clock { + my $response = qx/watchman clock "$git_work_tree"/; + die "Failed to get clock id on '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + + return $json_pkg->new->utf8->decode($response); +} + +sub watchman_query { + my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') + or die "open2() failed: $!\n" . + "Falling back to scanning...\n"; + + # In the query expression below we're asking for names of files that + # changed since $last_update_token but not from the .git folder. + # + # To accomplish this, we're using the "since" generator to use the + # recency index to select candidate nodes and "fields" to limit the + # output to file names only. Then we're using the "expression" term to + # further constrain the results. + my $last_update_line = ""; + if (substr($last_update_token, 0, 1) eq "c") { + $last_update_token = "\"$last_update_token\""; + $last_update_line = qq[\n"since": $last_update_token,]; + } + my $query = <<" END"; + ["query", "$git_work_tree", {$last_update_line + "fields": ["name"], + "expression": ["not", ["dirname", ".git"]] + }] + END + + # Uncomment for debugging the watchman query + # open (my $fh, ">", ".git/watchman-query.json"); + # print $fh $query; + # close $fh; + + print CHLD_IN $query; + close CHLD_IN; + my $response = do {local $/; }; + + # Uncomment for debugging the watch response + # open ($fh, ">", ".git/watchman-response.json"); + # print $fh $response; + # close $fh; + + die "Watchman: command returned no output.\n" . + "Falling back to scanning...\n" if $response eq ""; + die "Watchman: command returned invalid output: $response\n" . + "Falling back to scanning...\n" unless $response =~ /^\{/; + + return $json_pkg->new->utf8->decode($response); +} + +sub is_work_tree_watched { + my ($output) = @_; + my $error = $output->{error}; + if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { + $retry--; + my $response = qx/watchman watch "$git_work_tree"/; + die "Failed to make watchman watch '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + $output = $json_pkg->new->utf8->decode($response); + $error = $output->{error}; + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # close $fh; + + # Watchman will always return all files on the first query so + # return the fast "everything is dirty" flag to git and do the + # Watchman query just to get it over with now so we won't pay + # the cost in git to look up each individual file. + my $o = watchman_clock(); + $error = $output->{error}; + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + output_result($o->{clock}, ("/")); + $last_update_token = $o->{clock}; + + eval { launch_watchman() }; + return 0; + } + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + return 1; +} + +sub get_working_dir { + my $working_dir; + if ($^O =~ 'msys' || $^O =~ 'cygwin') { + $working_dir = Win32::GetCwd(); + $working_dir =~ tr/\\/\//; + } else { + require Cwd; + $working_dir = Cwd::cwd(); + } + + return $working_dir; +} diff --git a/passKitTests/Fixtures/password-store.git/hooks/post-update.sample b/passKitTests/Fixtures/password-store.git/hooks/post-update.sample new file mode 100755 index 0000000..ec17ec1 --- /dev/null +++ b/passKitTests/Fixtures/password-store.git/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/passKitTests/Fixtures/password-store.git/hooks/pre-applypatch.sample b/passKitTests/Fixtures/password-store.git/hooks/pre-applypatch.sample new file mode 100755 index 0000000..4142082 --- /dev/null +++ b/passKitTests/Fixtures/password-store.git/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/passKitTests/Fixtures/password-store.git/hooks/pre-commit.sample b/passKitTests/Fixtures/password-store.git/hooks/pre-commit.sample new file mode 100755 index 0000000..e144712 --- /dev/null +++ b/passKitTests/Fixtures/password-store.git/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=$(git hash-object -t tree /dev/null) +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --type=bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/passKitTests/Fixtures/password-store.git/hooks/pre-merge-commit.sample b/passKitTests/Fixtures/password-store.git/hooks/pre-merge-commit.sample new file mode 100755 index 0000000..399eab1 --- /dev/null +++ b/passKitTests/Fixtures/password-store.git/hooks/pre-merge-commit.sample @@ -0,0 +1,13 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git merge" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message to +# stderr if it wants to stop the merge commit. +# +# To enable this hook, rename this file to "pre-merge-commit". + +. git-sh-setup +test -x "$GIT_DIR/hooks/pre-commit" && + exec "$GIT_DIR/hooks/pre-commit" +: diff --git a/passKitTests/Fixtures/password-store.git/hooks/pre-push.sample b/passKitTests/Fixtures/password-store.git/hooks/pre-push.sample new file mode 100755 index 0000000..4ce688d --- /dev/null +++ b/passKitTests/Fixtures/password-store.git/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/passKitTests/Fixtures/password-store.git/hooks/pre-rebase.sample b/passKitTests/Fixtures/password-store.git/hooks/pre-rebase.sample new file mode 100755 index 0000000..6cbef5c --- /dev/null +++ b/passKitTests/Fixtures/password-store.git/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up to date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +<<\DOC_END + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/passKitTests/Fixtures/password-store.git/hooks/pre-receive.sample b/passKitTests/Fixtures/password-store.git/hooks/pre-receive.sample new file mode 100755 index 0000000..a1fd29e --- /dev/null +++ b/passKitTests/Fixtures/password-store.git/hooks/pre-receive.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to make use of push options. +# The example simply echoes all push options that start with 'echoback=' +# and rejects all pushes when the "reject" push option is used. +# +# To enable this hook, rename this file to "pre-receive". + +if test -n "$GIT_PUSH_OPTION_COUNT" +then + i=0 + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" + do + eval "value=\$GIT_PUSH_OPTION_$i" + case "$value" in + echoback=*) + echo "echo from the pre-receive-hook: ${value#*=}" >&2 + ;; + reject) + exit 1 + esac + i=$((i + 1)) + done +fi diff --git a/passKitTests/Fixtures/password-store.git/hooks/prepare-commit-msg.sample b/passKitTests/Fixtures/password-store.git/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000..10fa14c --- /dev/null +++ b/passKitTests/Fixtures/password-store.git/hooks/prepare-commit-msg.sample @@ -0,0 +1,42 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first one removes the +# "# Please enter the commit message..." help message. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +COMMIT_MSG_FILE=$1 +COMMIT_SOURCE=$2 +SHA1=$3 + +/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" + +# case "$COMMIT_SOURCE,$SHA1" in +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; +# *) ;; +# esac + +# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" +# if test -z "$COMMIT_SOURCE" +# then +# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" +# fi diff --git a/passKitTests/Fixtures/password-store.git/hooks/push-to-checkout.sample b/passKitTests/Fixtures/password-store.git/hooks/push-to-checkout.sample new file mode 100755 index 0000000..af5a0c0 --- /dev/null +++ b/passKitTests/Fixtures/password-store.git/hooks/push-to-checkout.sample @@ -0,0 +1,78 @@ +#!/bin/sh + +# An example hook script to update a checked-out tree on a git push. +# +# This hook is invoked by git-receive-pack(1) when it reacts to git +# push and updates reference(s) in its repository, and when the push +# tries to update the branch that is currently checked out and the +# receive.denyCurrentBranch configuration variable is set to +# updateInstead. +# +# By default, such a push is refused if the working tree and the index +# of the remote repository has any difference from the currently +# checked out commit; when both the working tree and the index match +# the current commit, they are updated to match the newly pushed tip +# of the branch. This hook is to be used to override the default +# behaviour; however the code below reimplements the default behaviour +# as a starting point for convenient modification. +# +# The hook receives the commit with which the tip of the current +# branch is going to be updated: +commit=$1 + +# It can exit with a non-zero status to refuse the push (when it does +# so, it must not modify the index or the working tree). +die () { + echo >&2 "$*" + exit 1 +} + +# Or it can make any necessary changes to the working tree and to the +# index to bring them to the desired state when the tip of the current +# branch is updated to the new commit, and exit with a zero status. +# +# For example, the hook can simply run git read-tree -u -m HEAD "$1" +# in order to emulate git fetch that is run in the reverse direction +# with git push, as the two-tree form of git read-tree -u -m is +# essentially the same as git switch or git checkout that switches +# branches while keeping the local changes in the working tree that do +# not interfere with the difference between the branches. + +# The below is a more-or-less exact translation to shell of the C code +# for the default behaviour for git's push-to-checkout hook defined in +# the push_to_deploy() function in builtin/receive-pack.c. +# +# Note that the hook will be executed from the repository directory, +# not from the working tree, so if you want to perform operations on +# the working tree, you will have to adapt your code accordingly, e.g. +# by adding "cd .." or using relative paths. + +if ! git update-index -q --ignore-submodules --refresh +then + die "Up-to-date check failed" +fi + +if ! git diff-files --quiet --ignore-submodules -- +then + die "Working directory has unstaged changes" +fi + +# This is a rough translation of: +# +# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX +if git cat-file -e HEAD 2>/dev/null +then + head=HEAD +else + head=$(git hash-object -t tree --stdin &2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --type=bool hooks.allowunannotated) +allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) +denycreatebranch=$(git config --type=bool hooks.denycreatebranch) +allowdeletetag=$(git config --type=bool hooks.allowdeletetag) +allowmodifytag=$(git config --type=bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero=$(git hash-object --stdin &2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/passKitTests/Fixtures/password-store.git/info/exclude b/passKitTests/Fixtures/password-store.git/info/exclude new file mode 100644 index 0000000..a5196d1 --- /dev/null +++ b/passKitTests/Fixtures/password-store.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/passKitTests/Fixtures/password-store.git/objects/pack/pack-6a8dbb253e7642cc425de97363624aab04882615.idx b/passKitTests/Fixtures/password-store.git/objects/pack/pack-6a8dbb253e7642cc425de97363624aab04882615.idx new file mode 100644 index 0000000000000000000000000000000000000000..7efd056d551ebf17f65647ce79e30d8731c56274 GIT binary patch literal 2724 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 AY5)KL literal 0 HcmV?d00001 diff --git a/passKitTests/Fixtures/password-store.git/packed-refs b/passKitTests/Fixtures/password-store.git/packed-refs new file mode 100644 index 0000000..5b72267 --- /dev/null +++ b/passKitTests/Fixtures/password-store.git/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled sorted +925eb0f6b19282b5f10dfe008e0062b4be6dd41a refs/heads/master diff --git a/passKitTests/Fixtures/password-store.git/refs/remotes/origin/master b/passKitTests/Fixtures/password-store.git/refs/remotes/origin/master new file mode 100644 index 0000000..7d10008 --- /dev/null +++ b/passKitTests/Fixtures/password-store.git/refs/remotes/origin/master @@ -0,0 +1 @@ +925eb0f6b19282b5f10dfe008e0062b4be6dd41a diff --git a/passKitTests/Models/PasswordStoreTest.swift b/passKitTests/Models/PasswordStoreTest.swift index c6550e8..3f1363d 100644 --- a/passKitTests/Models/PasswordStoreTest.swift +++ b/passKitTests/Models/PasswordStoreTest.swift @@ -13,9 +13,8 @@ import XCTest @testable import passKit final class PasswordStoreTest: XCTestCase { - private let remoteRepoURL = URL(string: "https://github.com/mssun/passforios-password-store.git")! - func testCloneAndDecryptMultiKeys() throws { + let remoteRepoURL = Bundle(for: type(of: self)).resourceURL!.appendingPathComponent("Fixtures/password-store.git") let url = Globals.sharedContainerURL.appendingPathComponent("Library/password-store-test/") Defaults.isEnableGPGIDOn = true diff --git a/plans/01-improve-test-coverage-plan.md b/plans/01-improve-test-coverage-plan.md index d9f8efe..4b197d4 100644 --- a/plans/01-improve-test-coverage-plan.md +++ b/plans/01-improve-test-coverage-plan.md @@ -42,12 +42,33 @@ This is standalone — it should be done before any other refactoring. ## Implementation -### 1. `PasswordStore` unit tests (highest priority) +### 1. Fixture password-store repo -The single existing test (`testCloneAndDecryptMultiKeys`) depends on network access. Add offline unit tests using a local git repo fixture: +A pre-built bare git repo checked into `passKitTests/Fixtures/password-store.git/`. Contains: -- **Setup/teardown**: Create a temp directory, `git init`, add `.gpg-id` + encrypted `.gpg` files, so tests don't need network. -- **Test `initPasswordEntityCoreData`**: Clone a local fixture repo → verify correct `PasswordEntity` tree in Core Data (names, paths, directories, parent-child relationships). +- A `.gpg-id` file with test key ID(s) +- Several `.gpg` files encrypted with the test keys from `TestPGPKeys` (at various directory depths) +- A subdirectory structure to exercise the BFS walk (nested folders, empty dirs) +- A git history with at least a couple of commits + +Since it's a bare repo, its contents (`HEAD`, `objects/`, `refs/`, etc.) are just regular files from the outer repo's perspective — no submodule issues. + +**Xcode project setup**: The fixture directory must be added to the Xcode project as a **folder reference** (blue folder) in the passKitTests target, and with "Build Rules" set to "Apply Once to Folder", so it's included in the "Copy Bundle Resources" build phase. In Xcode: drag the `Fixtures/` directory into the passKitTests group → select "Create folder references" → check only the passKitTests target. Without this, the files won't be accessible from the test bundle at runtime. + +To update the fixture, pull from origin (already set to `https://github.com/mssun/passforios-password-store.git`) or replace with any local bare repo: +```sh +# Update from origin +cd passKitTests/Fixtures/password-store.git +git fetch origin +git update-ref refs/heads/master origin/master + +# Or replace with a custom local repo +git clone --bare /path/to/local/repo passKitTests/Fixtures/password-store.git +``` + +### 2. `PasswordStore` unit tests (highest priority) + +- **Test `initPasswordEntityCoreData`**: Clone the fixture repo → verify correct `PasswordEntity` tree in Core Data (names, paths, directories, parent-child relationships). - **Test `deleteCoreData`**: Populate, then delete, verify empty. - **Test `eraseStoreData`**: Verify repo directory deleted, Core Data cleared, git handle nil'd. - **Test `erase`**: Verify full cleanup (keychain, defaults, passcode, PGP state). @@ -56,7 +77,7 @@ The single existing test (`testCloneAndDecryptMultiKeys`) depends on network acc - **Test `add` / `delete` / `edit`**: Verify filesystem + Core Data + git commit. - **Test `reset`**: Verify Core Data rebuilt to match filesystem after git reset. -### 2. `PasswordEntity` relationship tests +### 3. `PasswordEntity` relationship tests Extend `PasswordEntityTest` (already uses `CoreDataTestCase`): @@ -65,7 +86,7 @@ Extend `PasswordEntityTest` (already uses `CoreDataTestCase`): - **Test hidden files are skipped**. - **Test empty directories**. -### 3. `AppKeychain` tests +### 4. `AppKeychain` tests Basic tests against the real Keychain API (or a test wrapper): @@ -74,25 +95,21 @@ Basic tests against the real Keychain API (or a test wrapper): - **Test `contains`**. - **Test `removeAllContent(withPrefix:)`** — this method already exists and will be useful for per-store cleanup. -### 4. `PersistenceController` tests +### 5. `PersistenceController` tests - **Test `reinitializePersistentStore`** — verify existing data is gone after reinit. - **Test model loading** — verify the `.momd` loads correctly. -### 5. Test infrastructure: local git repo fixture builder - -A helper that creates a temp git repo with configurable `.gpg-id`, encrypted `.gpg` files, and directory structure. Replaces the current network-dependent clone in `PasswordStoreTest`. - --- ## Implementation Order -All steps are independent and can be done in parallel: - | Step | Description | |------|-------------| -| 1 | `PasswordStore` unit tests (offline, local git fixture) | -| 2 | `PasswordEntity` BFS walk + relationship tests | -| 3 | `AppKeychain` tests | -| 4 | `PersistenceController` tests | -| 5 | Local git repo fixture builder (prerequisite for step 1) | +| 1 | Fixture password-store bare repo | +| 2 | `PasswordStore` unit tests (uses fixture from step 1) | +| 3 | `PasswordEntity` BFS walk + relationship tests | +| 4 | `AppKeychain` tests | +| 5 | `PersistenceController` tests | + +Steps 2–5 can be done in parallel once step 1 is complete. Steps 3–5 are also independent of step 1.