// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package osutil

import (
	"bytes"
	"os"
	"path/filepath"
	"testing"
)

func TestCreateAtomicCreate(t *testing.T) {
	os.RemoveAll("testdata")
	defer os.RemoveAll("testdata")

	if err := os.Mkdir("testdata", 0o755); err != nil {
		t.Fatal(err)
	}

	w, err := CreateAtomic("testdata/file")
	if err != nil {
		t.Fatal(err)
	}

	n, err := w.Write([]byte("hello"))
	if err != nil {
		t.Fatal(err)
	}
	if n != 5 {
		t.Fatal("written bytes", n, "!= 5")
	}

	if _, err := os.ReadFile("testdata/file"); err == nil {
		t.Fatal("file should not exist")
	}

	if err := w.Close(); err != nil {
		t.Fatal(err)
	}

	bs, err := os.ReadFile("testdata/file")
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(bs, []byte("hello")) {
		t.Error("incorrect data")
	}
}

func TestCreateAtomicReplace(t *testing.T) {
	testCreateAtomicReplace(t, 0o666)
}

func TestCreateAtomicReplaceReadOnly(t *testing.T) {
	testCreateAtomicReplace(t, 0o444) // windows compatible read-only bits
}

func testCreateAtomicReplace(t *testing.T, oldPerms os.FileMode) {
	t.Helper()

	testdir := t.TempDir()
	testfile := filepath.Join(testdir, "testfile")

	os.RemoveAll(testdir)

	if err := os.Mkdir(testdir, 0o755); err != nil {
		t.Fatal(err)
	}

	if err := os.WriteFile(testfile, []byte("some old data"), oldPerms); err != nil {
		t.Fatal(err)
	}

	// Go < 1.14 has a bug in WriteFile where it does not use the requested
	// permissions on Windows. Chmod to make sure.
	if err := os.Chmod(testfile, oldPerms); err != nil {
		t.Fatal(err)
	}
	// Trust, but verify.
	if info, err := os.Stat(testfile); err != nil {
		t.Fatal(err)
	} else if info.Mode() != oldPerms {
		t.Fatalf("Wrong perms 0%o", info.Mode())
	}

	w, err := CreateAtomic(testfile)
	if err != nil {
		t.Fatal(err)
	}

	if _, err := w.Write([]byte("hello")); err != nil {
		t.Fatal(err)
	}
	if err := w.Close(); err != nil {
		t.Fatal(err)
	}

	bs, err := os.ReadFile(testfile)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(bs, []byte("hello")) {
		t.Error("incorrect data")
	}

	if info, err := os.Stat(testfile); err != nil {
		t.Fatal(err)
	} else if info.Mode() != oldPerms {
		t.Fatalf("Perms changed during atomic write: 0%o", info.Mode())
	}
}
