2023/03/21

Mock Open Write On Python

ๅœจ python ็š„ๆธฌ่ฉฆไธญ,ๆœ‰ๆ™‚ๅ€™ mock ๅช่ฆ mock a.b.c ๅฐฑๅฏไปฅๆˆๅŠŸ,ไฝ†ๆˆ‘ๅ€‘ไฝฟ็”จไบ† with ็š„ๆƒ…ๆณไธ‹,ๅฐฑๅฟ…้ ˆๅ€ŸๅŠฉ __enter__ ้€™ๅ€‹้ญ”่ก“ๆ–นๆณ•ไพ†ๆธฌ่ฉฆไบ†,ๆˆ‘ๅ€‘ไป‹็ดนๅ…ฉ็จฎๅฏซๆณ•。

my_open.py

def write(path, context):
    with open(path) as f:
        f.write(context)

test_my_open.py

import unittest
import my_open

from unittest.mock import mock_open, patch


class MyTestCase(unittest.TestCase):
    def test_my_open_1(self):
        with patch('builtins.open', mock_open()) as o:
            path = '/tmp/test.txt'
            context = 'hello'
            my_open.write(path, context)

            o.assert_called_once_with(path)
            o().write.assert_called_once_with(context)

    @patch('builtins.open')
    def test_my_open_2(self, mock_o: mock_open):
        path = '/tmp/test.txt'
        context = 'hello'
        my_open.write(path, context)

        mock_o.assert_called_once_with(path)
        handle = mock_o.return_value.__enter__.return_value
        handle.write.assert_called_once_with(context)


if __name__ == '__main__':
    unittest.main()

2023/02/07

Python unittest

ๆœ€่ฟ‘ๅœจๅนซๅ…ฌๅธ้–‹็™ผ,ไฝฟ็”จ python ่ฃฝไฝœๅ‡บ Windows ไฝฟ็”จ็š„ๅฐๅทฅๅ…ท,้ †ไพฟ k ไบ†ไธ€ไธ‹ๅ–ฎๅ…ƒๆธฌ่ฉฆ,ๆŠŠ้‡ๅˆฐ้Ž็š„ๆŠ€ๅทงๅšๅ€‹็ญ†่จ˜。

chan.py

import os
import shutil

import requests as requests


class Chan:
    def add(self, number1: int, number2: int) -> int:
        return number1 + number2

    def raise_method(self, result: str) -> str:
        if result != 'raise':
            return result

        raise Exception('exception raised')

    def a(self) -> str:
        return self.b()

    def b(self) -> str:
        return 'b'

    def move_file(self, from_path, to_path) -> object:
        return os.rename(from_path, to_path)

    def get_url(self, url) -> int:
        response = requests.get(url)

        return response.status_code

    def my_open(self, path) -> str:
        with open(path) as f:
            return f.read()

    def copy_twice(self, source: str, dest_dir: str) -> None:
        base_path = os.path.dirname(__file__)
        shutil.copyfile(source, os.path.join(base_path, dest_dir, 'first'))
        shutil.copyfile(source, os.path.join(base_path, dest_dir, 'second'))

    def scan_folder(self, path) -> list:
        files = []
        for item in os.listdir(os.path.join(path)):
            if os.path.isfile(os.path.join(path, item)):
                files.append(os.path.join(path, item))

        return files

tests/test_chan.py

import os.path
import unittest
from unittest.mock import patch, MagicMock, mock_open, call

from chan import Chan


def mock_response(*args, **kwargs) -> object:
    class Response:
        status_code = 500

    return Response()


class MyTestCase(unittest.TestCase):
    base_path: str

    def setUp(self) -> None:
        self.base_path = os.path.dirname(os.path.dirname(__file__))

    def test_add(self) -> None:
        chan = Chan()
        actual = chan.add(1, 2)
        self.assertEqual(3, actual)

    def test_raise_should_not_happened(self) -> None:
        chan = Chan()
        actual = chan.raise_method('test')
        self.assertEqual('test', actual)

    def test_raise_should_happened(self) -> None:
        chan = Chan()
        self.assertRaises(Exception, chan.raise_method, 'raise')

    def test_raise_error_message(self) -> None:
        chan = Chan()
        with self.assertRaises(Exception) as error:
            chan.raise_method('raise')

        self.assertEqual('exception raised', str(error.exception))

    @patch.object(Chan, 'b', MagicMock(return_value='c'))
    def test_mock_method(self) -> None:
        chan = Chan()
        actual = chan.a()
        self.assertEqual('c', actual)

    @patch.object(Chan, 'b')
    def test_mock_method_called_once_by_injection(self, mock_b: MagicMock) -> None:
        mock_b.return_value = 'c'
        chan = Chan()
        actual = chan.a()
        self.assertEqual('c', actual)
        self.assertTrue(mock_b.called)

    def test_mock_method_called_once_by_with(self) -> None:
        with patch.object(Chan, 'b') as check:
            check.return_value = 'c'
            chan = Chan()
            actual = chan.a()
            self.assertEqual('c', actual)
            check.assert_called_once()

    @patch('chan.os.rename', MagicMock(return_value='moved'))
    def test_os_method(self) -> None:
        chan = Chan()
        actual = chan.move_file('1.txt', '2.txt')
        self.assertEqual('moved', actual)

    @patch('chan.requests.get', MagicMock(side_effect=mock_response))
    def test_requests_get(self) -> None:
        chan = Chan()
        actual = chan.get_url('https://www.google.com')
        self.assertEqual(500, actual)

    @patch('chan.open', mock_open(read_data='ok'))
    def test_open(self) -> None:
        chan = Chan()
        actual = chan.my_open('path')
        self.assertEqual('ok', actual)

    @patch('chan.open', new_callable=mock_open, read_data='ok')
    def test_open_by_injection(self, m) -> None:
        chan = Chan()
        actual = chan.my_open('path')
        self.assertEqual('ok', actual)
        self.assertTrue(m.called)

    @patch('chan.shutil.copyfile')
    def test_copy_twice(self, mock_copyfile: MagicMock) -> None:
        chan = Chan()
        source = 'test.zip'
        chan.copy_twice(source, 'test_path')

        expected = [call(source, os.path.join(self.base_path, 'test_path', 'first')),
                    call(source, os.path.join(self.base_path, 'test_path', 'second'))]

        self.assertEqual(expected, mock_copyfile.call_args_list)
        self.assertEqual(2, mock_copyfile.call_count)

    @patch('chan.os.path.isfile')
    @patch('chan.os.listdir')
    def test_scan_folder(self, mock_listdir: MagicMock, mock_isfile: MagicMock) -> None:
        mock_listdir.return_value = ['dir1', 'file1', 'dir2', 'file2']
        mock_isfile.side_effect = [False, True, False, True]

        chan = Chan()
        test_dir = 'test_dir'
        actual = chan.scan_folder('test_dir')
        expected = [os.path.join(test_dir, 'file1'), os.path.join(test_dir, 'file2')]
        self.assertEqual(expected, actual)


if __name__ == '__main__':
    unittest.main()

2023/01/10

Powershell Note

Windows ็š„ cmd ไธๆ˜ฏๅพˆๅฅฝ็”จ,ๆˆ‘้ธๆ“‡ไฝฟ็”จ PoweShell ๅœจ Windows ๅŸท่กŒ command line,ไป–้‚„ๆ˜ฏๆฒ’ๆœ‰ Linux ็š„ cli ๅฅฝ็”จ,ไฝ†็‚บไบ†ๅฟซ้€Ÿๅœจ Window s ้”ๅˆฐๆŸไบ›ๅ‹•ไฝœ,้‚„ๆ˜ฏไพ†ๅญธไธ€ไธ‹ๅŸบๆœฌๆŒ‡ไปคไธฆไธ”ๅšๅ€‹็ญ†่จ˜。

ๆœๅฐ‹ๅ…งๆ–‡,้กžไผผ grep

ls -Path ./ -r | sls "WORD" | select Path -u

ไธŠ้ข็š„ๆ•ˆๆžœๆ˜ฏ็”จ ls -r fetch ่ฉฒ็›ฎ้Œ„็š„ๆ‰€ๆœ‰ๆช”ๆกˆ,ๅ†้€้Ž sls ็š„ pipline ๅœจๆฏๅ€‹ๆ’ˆๅ‡บไพ†็š„ๆช”ๆกˆไธญๆ‰พๅฐ‹ๅ…งๆ–‡็ฌฆๅˆ็š„ๅ…งๅฎน,ๆœ€ๅพŒๅˆฉ็”จ select Path -u ๅฐ‡้‡่ค‡็š„ๆช”ๅ้Žๆฟพ,ๅœจ PowerShell ่ฃก้ขๆฒ’ๆœ‰ๅคงๅฐๅฏซไน‹ๅˆ†。

ๆœๅฐ‹ๆช”ๆกˆ,้กžไผผ find

ls -Path ./ -r -Filter "WORD" -Name

ๅœจ่ฉฒ็›ฎ้Œ„ไธ‹ๆœๅฐ‹ๆช”ๆกˆๅ็จฑ。

2023/01/04

Vagrant SSH Issue

ๆˆ‘็š„้–‹็™ผ็’ฐๅขƒ้ƒฝๆ˜ฏ base on Vagrant,ๆŸไธ€ๅคฉ Windows ๆ›ดๆ–ฐๅพŒไฝฟ็”จ vagrant ssh ๆ™‚็„กๆณ•็™ปๅ…ฅ่™›ๆฉŸๅ…ง,ๆœฌไปฅ็‚บๆ˜ฏๆ›ดๆ–ฐๅˆฐ 2.3.4 ๅพŒ็š„ bug,ๅ› ๆญค็™ผไบ†้€™็ฏ‡ issue,็ถ“้Žๅนพๅ€‹ๅ›žๅˆ็š„่จŽ่ซ–ๅพŒ,็™ผ็พๅ•้กŒๅ‡บๅœจ Windows OpenSSH ๆ›ดๆ–ฐๅพŒๅธถไพ†็š„ๅฝฑ้Ÿฟ,ๅ› ๆญค่ฆๆญฃๅธธไฝฟ็”จ็š„่ฉฑๆœ‰ๅนพๅ€‹ๆ–นๆณ•。

  1. ๅฐ‡ Vagrantfile ็›ธ้—œ็š„่ณ‡ๆ–™็›ฎ้Œ„ๆฌๅˆฐไฝ ็š„ๅ€‹ไบบ็›ฎ้Œ„ไธ‹,ๅฆ‚ C:\Users\username\vagrant。
  2. ไฝฟ็”จ vagrant ่‡ช่บซ็š„ embedded ssh,ไธ€่ˆฌ command prompt ็š„่ฉฑ่จญๅฎš SET VAGRANT_PREFER_SYSTEM_BIN=0,PowerShell ็š„่ฉฑ key ๅ…ฅ $Env:VAGRANT_PREFER_SYSTEM_BIN=0,็ถ“ๆธฌ่ฉฆ้ƒฝๅฏไปฅ้ †ๅˆฉ้€ฒๅ…ฅ่™›ๆฉŸ。

2022/12/05

From Docker Virtual Network Connect To Solid MySQL Server

ๅ‡่จญๆˆ‘ๅ€‘็š„ server ๅชๆœ‰ไธ€ๅฐ,PHP ็’ฐๅขƒ็‚บ docker container,่€Œ่ณ‡ๆ–™ๅบซ็‚บๅฏฆ้ซ”ๆฉŸๅฎ‰่ฃ,่ณ‡ๆ–™ๅบซ 3306 ้˜ฒ็ซ็‰†้˜ฒๅ ตไธๅฐๅค–,ไฝ†่ฆๅ…่จฑๅพž Docker ็’ฐๅขƒๅ…ง็š„ๆœๅ‹™ๅญ˜ๅ–,ๆœ€็ฐกๅ–ฎ็š„ๆ–นๅผๅฐฑๆ˜ฏ้˜ฒ็ซ็‰†่ฎ“ Docker ็”ข็”Ÿ็š„ network interface ้€š้Ž,ไธ‹้ขๆˆ‘ๅ€‘ไพ†ๅฏฆๅš้€™ไปถไบ‹。

ไปŠๅคฉ็คบ็ฏ„็’ฐๅขƒ็‚บไฝฟ็”จ Vagrant ่ตทไธ€ๅฐ Ubuntu jammy64,็„ถๅพŒๅœจ่ฃก้ข้€ฒ่กŒ้˜ฒ็ซ็‰†็š„ๆธฌ่ฉฆ。

sudo service ufw start
sudo ufw enable
sudo ufw allow ssh

่จ˜ๅพ—้–‹ๅ•Ÿ้˜ฒ็ซ็‰†่ฆๅ…ˆๆŠŠ ssh ๆ‰“้–‹,ไธ็„ถๅฐฑๆ‚ฒๅЇไบ†,ๆŽฅ่‘—ๅฎ‰่ฃ MySQL,docker ไปฅๅŠ docker compose ้ƒจๅˆ†ๅฐฑ้บป็…ฉ่‡ช่กŒๅฎ‰่ฃ。

sudo apt install -y mariadb-server mariadb-client

ๅฎ‰่ฃๅฎŒ็•ขๅพŒ,ๆˆ‘ๅ€‘ไพ†ๅปบ็ซ‹ๆธฌ่ฉฆ็”จ่ณ‡ๆ–™ๅบซไปฅๅŠๅธณ่™Ÿ。

sudo mysql
create database laravel;
GRANT ALL PRIVILEGES ON `laravel`.* TO 'homestead'@'%' IDENTIFIED BY 'secret' WITH GRANT OPTION;

้€™้‚ŠๆฌŠ้™้–‹ % ๆฒ’้—œไฟ‚,ๅ› ็‚บๆˆ‘ๅ€‘ๅชๆœƒๅ…่จฑ docker network ๆŒ‡ๅฎš็š„ network interface ้€ฒๅ…ฅ่€Œๅทฒ。

docker-compose.yml
version: "3.9"

services:
  fpm:
    container_name: fpm
    build: .
    restart: always
    working_dir: /www
    extra_hosts:
      - "host.docker.internal:host-gateway"
    networks:
      - front_end
    volumes:
      - ./www/:/www

networks:
  front_end:
    driver_opts:
      com.docker.network.bridge.name: br-fpm
Dockerfile
FROM php:8.1-fpm

RUN apt-get update && apt-get install -y \
    git \
    curl \
    libpng-dev \
    libonig-dev \
    libxml2-dev \
    zip \
    unzip

ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/

RUN chmod +x /usr/local/bin/install-php-extensions && \
    install-php-extensions @composer pdo_mysql mbstring exif pcntl bcmath gd

WORKDIR /www

้€™ๆ˜ฏ่ฆๅฎ‰่ฃ Laravel ็š„ๆœ€ๅฐ็’ฐๅขƒ,ๆˆ‘ๅ€‘ไปŠๅคฉๅชๆ˜ฏ่ฆๆธฌ่ฉฆ้€ฃ็ทšๆ‰€ไปฅๅ…ถไป–็š„่จญๅฎšๅฐฑๅ…ˆไธ็†ๆœƒไบ†。

sudo docker compose up -d

็ญ‰ image build ๅฎŒไปฅๅŠ service ่ตทไพ†ไปฅๅพŒ,ๆˆ‘ๅ€‘ไพ†ๅˆฉ็”จๆŒ‡ไปคๅฎ‰่ฃไธ€ๅ€‹ Laravel ็š„็’ฐๅขƒ。

sudo docker compose exec fpm composer create-project laravel/laravel .

ๆŽฅ่‘—ๆˆ‘ๅ€‘ๆ‰“้–‹ .env ๆช”ๆกˆ่จญๅฎš่ณ‡ๆ–™ๅบซ้€ฃ็ทšๅ…งๅฎน。

DB_CONNECTION=mysql
DB_HOST=host.docker.internal
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=homestead
DB_PASSWORD=secret

้€™ๆ™‚ไพ†ๆธฌ่ฉฆไธ€ไธ‹้€ฃ็ทš。

sudo docker compose exec fpm php artisan migrate

ๅ› ็‚บ้˜ฒ็ซ็‰†ๆ“‹ไฝไบ†,ๆ‰€ไปฅ connection ๆœƒ hang ไฝ,ๆœ€็ต‚ๅคฑๆ•—,ๆŽฅ่‘—ไพ†้–‹ๅง‹้˜ฒ็ซ็‰†่จญๅฎš。

sudo ufw allow in on br-fpm to any port 3306

ๆŽฅ่‘—ๅ†ๅŸท่กŒไธ€ๆฌก migrate ็š„ๆŒ‡ไปค。

sudo docker compose exec fpm php artisan migrate

   INFO  Preparing database.

  Creating migration table ............................................................................................................... 10ms DONE

   INFO  Running migrations.

  2014_10_12_000000_create_users_table .................................................................................................... 9ms DONE
  2014_10_12_100000_create_password_resets_table .......................................................................................... 2ms DONE
  2019_08_19_000000_create_failed_jobs_table .............................................................................................. 3ms DONE
  2019_12_14_000001_create_personal_access_tokens_table ................................................................................... 4ms DONE

ๅฏไปฅ็œ‹ๅˆฐ migration ้ †ๅˆฉๆˆๅŠŸไบ†,ๅฆ‚ๆžœ็™ผ็”Ÿ connection refuse ็š„้Œฏ่ชค็š„่ฉฑ,่จ˜ๅพ—ๆŠŠ MySQL ็š„ bind-address ่จญๅฎš็‚บ 0.0.0.0。

iptable ็š„่ฉฑ็”จไธ‹ๆ–น็š„ๆŒ‡ไปคๆ‡‰ๅฏ้”ๅˆฐไธ€ๆจฃ็š„ๆ•ˆๆžœ。

iptables -A INPUT -i br-fpm -p tcp --dport 3306 -j ACCEPT
iptables -A INPUT -p tcp --dport 3306 -j DROP

2022/11/21

Test API Server With Express.js

ๆธฌ่ฉฆ API ็ตๆžœๆ™‚ๅธธๅธธ้œ€่ฆ fake data ๅšๅฏฆ้š›ๆธฌ่ฉฆ,ๆžถ่จญ http server ๆœ‰้ปžๅฐ้กŒๅคงไฝœ,ๆˆ‘ๅ€‘ๅฏไปฅๅˆฉ็”จ express.js ่ฟ…้€Ÿๆญๅปบๆธฌ่ฉฆ็”จ็š„ API server。

้ฆ–ๅ…ˆๅฎ‰่ฃ็›ธ้—œๅฅ—ไปถ:

npm i --save express cors

ๆŽฅ่‘—ๅปบ็ซ‹ไธ€ๆ”ฏ server.js,็จ‹ๅผๅฆ‚ไธ‹:

const express = require('express');
const app = express();
const port = 8080;
const fs = require("fs");
const path = require("path");
const cors = require("cors");

app.use(cors());

function getFile(fileName) {
    return JSON.parse(fs.readFileSync(path.join(__dirname, "data", `${fileName}.json`)));
}

app.get('/api', (req, res) => {
    res.json(getFile('index'));
});

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`);
})

index.json ๅ…งๅฎนๅฆ‚ไธ‹:

{
    "status": true,
    "data": "hello world"
}

ๆœ‰ๆ™‚ๅ€™ json ็š„ๅ…งๅฎนๅพˆ้พๅคง,ๆˆ‘ๅ€‘ไธๅธŒๆœ›่ฒ ่ฒฌ route ็š„ๆช”ๆกˆ้•ทๅบฆ้Ž้•ทไธๅฅฝ็ถญ่ญท,ๆ‰€ไปฅๆˆ‘้€™้‚Š parse ไบ† data ็›ฎ้Œ„่ฃกๆŒ‡ๅฎš็š„ .json ๆช”ๆกˆ่ฟ”ๅ›ž,ๆŽฅ่‘—ๅช่ฆๅŸท่กŒ node server.js ๅพŒๅฐฑๅฏไปฅๅœจ http://localhost:8080/api ็œ‹ๅˆฐๆญฃ็ขบ็š„ๅ…งๅฎนไบ†。

2022/11/17

CSS Practice - Learn CSS Grid

ๆœ€่ฟ‘ๅœจๅคง้‡็ทด็ฟ’ CSS 3 ๅˆ‡็‰ˆ,็œ‹ๅˆฐๆœ‰่ถฃ็š„็‰ˆ้ขๅฐฑๆ‹ฟไพ†ๆ™‚ๅš็ทด็ฟ’,Learn CSS Grid ้€™ๅ€‹็‚ธ้–‹็š„ๆ•ˆๆžœๆŒบๆœ‰่ถฃ็š„,่ค‡่ฃฝไบ† html ๆžถๆง‹ๅพŒ,้™คไบ†ไธ€ไบ›้ก่‰ฒ่ทŸ้–“่ทๆ•ธๅ€ผไปฅๅค–็š„ css ๅฐฑๆ†‘ๆƒณๅƒๆ‰‹ๅˆปไบ†,้™คไบ†็‚ธ่ฃ‚็š„ไฝ็ฝฎๅชๆŠ“ๅ€‹ๅคงๆฆ‚ไปฅๅค–,ๆŽ’็‰ˆ็š„้ƒจๅˆ†็›ก้‡ไธ€่‡ด。