Skip to content

Conversation

josecelano
Copy link
Member

This PR replaces this other one.

@ivanramosnet your branch needed to be rebased and I did not want to bother you since I know you are pretty busy. I was going to apply your changes but I decided to explore a different solution. It's basically the same core patch we discussed in the issue but I extracted a git wrapper class. I think the wrapper could be useful to detect more cases like these and I've added an explanation of why we need to do that.

…mand wrapper

This wrapper is going to be used to detect Git repository statuses,
like:

- Not initialized repo in dir.
- No commits.
@josecelano
Copy link
Member Author

josecelano commented Jun 1, 2022

One unit test is failing. I think it's the same problem we detected in the previous PR.

Probably we need to add the committer identity for testing in the new Git wrapper.

I suppose we could do it in the workflow:

- name: Set up git committer identity
  run: |
    git config --global user.name 'github-actions[bot]'
    git config --global user.email 'github-actions[bot]@users.noreply.github.com'

as we do it for e2e-tests. But I think a better option would be just to pass some env variables the Git process with the commiter name and email, by using the env attribute.
This problem relates to #219

And there is another integration test failing because of this issue.

@josecelano
Copy link
Member Author

I've fixed the unit test. The integration test should not fail in the upstream repo and it will be fixed with this issue so I'm going to merge this PR.

@josecelano josecelano merged commit 281c2bf into nautilus-cyberneering:main Jun 1, 2022
@josecelano josecelano deleted the issue-198-fix-code-scanning-alert branch June 1, 2022 16:25
expect(checkStatus).toThrowError()
})

it('should fail running `git log` when the repo has been initialized but it does not have a ny commits', async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tiny little typo: "a ny"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank @yeraydavidrodriguez I will open the issue again and fix it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed with #259

git.init()
git.setLocalConfig('user.name', 'A committer')
git.setLocalConfig('user.email', 'committer@example.com')
git.emptyCommit('not relevant')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about it, but here we are testing two inter-dependent things in the same test: that 'git log' do not fail when there is a commit and that the emptyCommit actually creates the commit.
What if emptyCommit creates the commit but not as we want? Maybe we should test it independently.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi @yeraydavidrodriguez you are totally right. I do not like it either.

In general, I do not think it's a problem to use other classes or the class you are testing in your tests. Especially if you want to reach a complex state for the class you want to test. For example, you have to use the constructor and the constructor could have a bug and make your test fail. Or you could need some initialization before calling the method you want to test.

But in this case, I agree the test would be better if we create the state with an external helper. And not only for the commit but for the rest of the needed steps: init, committer configuration and commit. In fact, there is something even worse, those new methods: init, setLocalConfig and emptyCommit are only used in tests.

The reason I kept it that way is:

  • I think in the long term, those methods could be useful also for production code.
  • Sometimes I add methods to production classes in order to improve their testability.

I suppose the right way to do it would have been:

Keep the production class simple:

export class Git {
  private readonly dir: GitRepoDir

  constructor(dir: GitRepoDir {
    this.dir = dir
  }

  execSync(args?: readonly string[]): string {
    const cmd = `git`
    const options: ExecFileSyncOptions = {
      stdio: 'pipe',
      shell: false,
      cwd: this.dir.getDirPath(),
      ...(typeof this.env !== 'undefined' && {env: this.env})
    }

    return execFileSync(cmd, args, options).toString()
  }

  status(): string {
    const args: readonly string[] = ['status']
    return this.execSync(args)
  }

  log(): string {
    const args: readonly string[] = ['log', '-n', '0']
    return this.execSync(args)
  }
}

And create a new helper class for testing:

export class GitWrapper {
  private readonly dir: GitRepoDir
  private readonly env: NodeJS.ProcessEnv | undefined

  constructor(dir: GitRepoDir, env?: NodeJS.ProcessEnv | undefined) {
    this.dir = dir
    this.env = env
  }

  execSync(args?: readonly string[]): string {
    const cmd = `git`
    const options: ExecFileSyncOptions = {
      stdio: 'pipe',
      shell: false,
      cwd: this.dir.getDirPath(),
      ...(typeof this.env !== 'undefined' && {env: this.env})
    }

    return execFileSync(cmd, args, options).toString()
  }

  init(): string {
    const args: readonly string[] = ['init']
    return this.execSync(args)
  }

  emptyCommit(message: string): string {
    const args: readonly string[] = [
      'commit',
      '--allow-empty',
      '-m',
      `"${message}"`
    ]
    return this.execSync(args)
  }

  setLocalConfig(key: string, value: string): string {
    const args: readonly string[] = ['config', `${key}`, `"${value}"`]
    return this.execSync(args)
  }
}

We could use this Git even more simple wrapper to build the status we want to test in a Git repo. If at some point we need those methods in the production class we can copy them.

In fact, we could write a builder to do things like:

const gitStatusBuilder: GitWrapper = new GitStatusBuidler().
  .fromNewEmptyDir().
  .init().
  .setLocalConfig("user.name", "A Committer")
  .setLocalConfig("user.email", "committer@email.com")
  .emptyCommit("message").
  .build()

Internally would do:

cd /tmp
mkdir random_name
cd random_name
git init
git commit --allow-empty -m "message"

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A very nice approach that last one. Thanks for the clarifications.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi @yeraydavidrodriguez in the end I realized that Simple-Git has a raw method to execute custom git commands. I'm using that method instead of adding a new Git wrapper.

Changes are implemented in this PR #259

Regarding the GitStatusBuidler we do not have any complex scenario right now. FOr now, I'm also using the raw method. See here.

This is the sample test where I need to create a new commit:

  it('should check if an initialized repo has commits', async () => {
    const gitRepoDir = new GitRepoDir(await createTempEmptyDir())
    const git = await newSimpleGitWithCommitterIdentity(gitRepoDir)

    const gitRepo = new GitRepo(gitRepoDir, git)
    await gitRepo.init()

    expect(await gitRepo.hasCommits()).toBe(false)

    await git.raw(
      'commit',
      '--no-gpg-sign',
      '--allow-empty',
      '--message',
      'Initial commit'
    )

    expect(await gitRepo.hasCommits()).toBe(true)
  })

If we need to create more complex git repo states for testing we can create the GitStatusBuidler but using also Simple-Git.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, @josecelano , it seems a good choice to avoid adding unnecessary complexity layers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Fix code scanning alert - Shell command built from environment values
2 participants