Happy path vs. edge case
The happy path is the run where everything goes right. Edge cases are the boundary inputs (empty, huge, zero, weird) that break it.
See it
What it is
The happy path is the one run where the user does the expected thing and every system cooperates: valid input, results found, card approved. It is the run you demo, and it is usually the only run a generated feature was written for. Edge cases are the runs out at the boundaries: zero rows, ten thousand rows, a name with an apostrophe, a quantity of -1, a token that expires between the click and the response. In test terms, the happy path is one test; the edges are the other fifteen, and they are where the bugs actually live.
The productive move is to enumerate edges by category instead of discovering them in production. Zero, one, many: empty list, single item, a thousand items. Boundaries: exactly at the limit, one below, one above (off-by-one bugs cluster here, which is why boundary value testing is its own named technique). Shape: empty string, whitespace only, emoji, 500 characters, null, wrong type. Failure: timeout, 500, partial data, offline. Timing: expired session, duplicate submit, response arriving after the user left. A table-driven test with one row per case keeps all of this to about twenty lines.
Two traps. Individually each edge is a one percent case; together they are most of your bug reports, so 'that never happens' is a prediction, not a fact. And a suite of happy-path tests gives high coverage numbers while proving almost nothing: coverage counts lines executed, not conditions considered.
Ask AI for it
Write a test suite for [FUNCTION / ENDPOINT] that covers one happy path plus explicit edge cases, as a table-driven test with one named case per row. Cover at minimum: empty input, single item, and a large collection; boundary values exactly at, one below, and one above each limit; malformed shapes (empty string, whitespace only, null, unicode and emoji, wrong type, very long string); invalid values (zero, negative, duplicate); and failure paths (dependency times out, returns 500, returns partial data). For each case assert the exact expected result or the exact error, never just 'does not throw'. List the cases you chose and any you deliberately skipped before the code.